diff options
869 files changed, 9221 insertions, 3974 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c5cd006a289..0afd59ab007 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -669,8 +669,8 @@ gitlab:assets:compile: - //@gitlab/gitlabhq - //@gitlab/gitlab-ee tags: - - gitlab-org-delivery - - high-cpu + - docker + - gitlab-org karma: <<: *dedicated-no-docs-pull-cache-job diff --git a/.gitlab/issue_templates/Doc Review.md b/.gitlab/issue_templates/Doc Review.md new file mode 100644 index 00000000000..14ab0c82d59 --- /dev/null +++ b/.gitlab/issue_templates/Doc Review.md @@ -0,0 +1,20 @@ +<!-- This issue requests a technical writer review as required for documentation + content that was merged without one. --> + +<!-- NOTE: Please add a DevOps stage label (format `devops:<stage_name>`) + and assign the technical writer who is + [listed for that stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). --> + + +## References + +Merged MR that introduced documentation requiring review: + +Related issue(s): + +## Further Details + +<!-- Any additional context, questions, or notes for the technical writer. --> + + +/label ~Documentation ~docs-review diff --git a/.gitlab/issue_templates/Documentation.md b/.gitlab/issue_templates/Documentation.md index b33ed5bcaa8..c0919aeeda4 100644 --- a/.gitlab/issue_templates/Documentation.md +++ b/.gitlab/issue_templates/Documentation.md @@ -1,54 +1,53 @@ -<!--See the general documentation guidelines https://docs.gitlab.com/ee/development/documentation --> +<!-- -<!-- Mention "documentation" or "docs" in the issue title --> +* Use this issue template for suggesting new docs or updates to existing docs. + Note: Doc work as part of feature development is covered in the Feature Request template. + +* For issues related to features of the docs.gitlab.com site, see + https://gitlab.com/gitlab-com/gitlab-docs/issues/ -<!-- Use this description template for new docs or updates to existing docs. --> +* For information about documentation content and process, see + https://docs.gitlab.com/ee/development/documentation/ --> -<!-- Check the documentation structure guidelines for guidance: https://docs.gitlab.com/ee/development/documentation/structure.html--> +### Type of issue -- [ ] Documents Feature A <!-- feature name --> -- [ ] Follow-up from: #XXX, !YYY <!-- Mention related issues, MRs, and epics when available --> +<!-- Un-comment the line for the applicable doc issue type to add its label. + Note that all text on that line is deleted upon issue creation. --> +<!-- /label ~"docs:fix" - Correction or clarification needed. --> +<!-- /label ~"docs:new" - New doc needed to cover a new topic or use case. --> +<!-- /label ~"docs:improvement" - Improving an existing doc; e.g. adding a diagram, adding or rewording text, resolving redundancies, cross-linking, etc. --> +<!-- /label ~"docs:revamp" - Review a page or group of pages in order to plan and implement major improvements/rewrites. --> +<!-- /label ~"docs:other" - Anything else. --> -## New doc or update? +### Problem to solve -<!-- Mark either of these boxes: --> +<!-- Include the following detail as necessary: +* What product or feature(s) affected? +* What docs or doc section affected? Include links or paths. +* Is there a problem with a specific document, or a feature/process that's not addressed sufficiently in docs? +* Any other ideas or requests? +--> -- [ ] New documentation -- [ ] Update existing documentation +### Further details -## Checklists +<!-- +* Any concepts, procedures, reference info we could add to make it easier to successfully use GitLab? +* Include use cases, benefits, and/or goals for this work. +* If adding content: What audience is it intended for? (What roles and scenarios?) + For ideas, see personas at https://design.gitlab.com/research/personas or the persona labels at + https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A +--> -### Product Manager +### Proposal -<!-- Reference: https://docs.gitlab.com/ee/development/documentation/workflow.html#1-product-manager-s-role-in-the-documentation-process --> +<!-- Further specifics for how can we solve the problem. --> -- [ ] Add the correct labels -- [ ] Add the correct milestone -- [ ] Indicate the correct document/directory for this feature <!-- (ping the tech writers for help if you're not sure) --> -- [ ] Fill the doc blurb below +### Who can address the issue -#### Documentation blurb +<!-- What if any special expertise is required to resolve this issue? --> -<!-- Documentation template: https://docs.gitlab.com/ee/development/documentation/structure.html#documentation-template-for-new-docs --> +### Other links/references -- Doc **title** - - <!-- write the doc title here --> - -- Feature **overview/description** - - <!-- Write the feature overview here --> - -- Feature **use cases** - - <!-- Write the use cases here --> - -### Developer - -<!-- Reference: https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process --> - -- [ ] Copy the doc blurb above and paste it into the doc -- [ ] Write the tutorial - explain how to use the feature -- [ ] Submit the MR using the appropriate MR description template +<!-- E.g. related GitLab issues/MRs --> /label ~Documentation diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md index 1bb8d33ff63..9c86dff835f 100644 --- a/.gitlab/issue_templates/Feature proposal.md +++ b/.gitlab/issue_templates/Feature proposal.md @@ -1,45 +1,54 @@ ### Problem to solve -<!--- What problem do we solve? --> +<!-- What problem do we solve? --> ### Target audience -<!--- For whom are we doing this? Include a [persona](https://design.gitlab.com/research/personas) +<!--- For whom are we doing this? Include a [persona](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/) listed below, if applicable, along with its [label](https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A), or define a specific company role, e.g. "Release Manager". Existing personas are: (copy relevant personas out of this comment, and delete any persona that does not apply) -- Parker, Product Manager, https://design.gitlab.com/research/personas#persona-parker +- Parker, Product Manager, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#parker-product-manager /label ~"Persona: Product Manager" -- Delaney, Development Team Lead, https://design.gitlab.com/research/personas#persona-delaney +- Delaney, Development Team Lead, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#delaney-development-team-lead /label ~"Persona: Development Team Lead" -- Sasha, Software Developer, https://design.gitlab.com/research/personas#persona-sasha +- Sasha, Software Developer, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sasha-software-developer /label ~"Persona: Software developer" -- Devon, DevOps Engineer, https://design.gitlab.com/research/personas#persona-devon +- Devon, DevOps Engineer, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#devon-devops-engineer /label ~"Persona: DevOps Engineer" -- Sidney, Systems Administrator, https://design.gitlab.com/research/personas#persona-sidney +- Sidney, Systems Administrator, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sidney-systems-administrator /label ~"Persona: Systems Administrator" -- Sam, Security Analyst, https://design.gitlab.com/research/personas#persona-sam +- Sam, Security Analyst, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sam-security-analyst /label ~"Persona: Security Analyst" --> ### Further details -<!--- Include use cases, benefits, and/or goals (contributes to our vision?) --> +<!-- Include use cases, benefits, and/or goals (contributes to our vision?) --> ### Proposal -<!--- How are we going to solve the problem? Try to include the user journey! --> +<!-- How are we going to solve the problem? Try to include the user journey! https://about.gitlab.com/handbook/journeys/#user-journey --> + +### Permissions and Security + +What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)? + +### Documentation + +<!-- See the Feature Change Documentation Workflow https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html +Add all known Documentation Requirements here, per https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#documentation-requirements --> ### What does success look like, and how can we measure that? -<!--- Define both the success metrics and acceptance criteria. Note that success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this --> +<!-- Define both the success metrics and acceptance criteria. Note that success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this. --> ### Links / references diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md index aaa16145399..9946651075f 100644 --- a/.gitlab/issue_templates/Security developer workflow.md +++ b/.gitlab/issue_templates/Security developer workflow.md @@ -30,6 +30,7 @@ Set the title to: `Description of the original issue` #### Documentation and final details - [ ] Check the topic on #security to see when the next release is going to happen and add a link to the [links section](#links) +- [ ] Add links to this issue and your MRs in the description of the security release issue - [ ] Find out the versions affected (the Git history of the files affected may help you with this) and add them to the [details section](#details) - [ ] Fill in any upgrade notes that users may need to take into account in the [details section](#details) - [ ] Add Yes/No and further details if needed to the migration and settings columns in the [details section](#details) diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index 4457821e23e..ba9624aeeab 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -1,33 +1,39 @@ -<!--See the general documentation guidelines https://docs.gitlab.com/ee/development/documentation --> +<!-- Follow the documentation workflow https://docs.gitlab.com/ee/development/documentation/workflow.html --> +<!-- Additional information is located at https://docs.gitlab.com/ee/development/documentation/ --> <!-- Mention "documentation" or "docs" in the MR title --> - -<!-- Use this description template for new docs or updates to existing docs. For changing documentation location use the "Change documentation location" template --> +<!-- For changing documentation location use the "Change documentation location" template --> ## What does this MR do? -<!-- Briefly describe what this MR is about --> +<!-- Briefly describe what this MR is about. --> ## Related issues -<!-- Mention the issue(s) this MR closes or is related to --> - -Closes +<!-- Link related issues below. Insert the issue link or reference after the word "Closes" if merging this should automatically close it. --> ## Author's checklist -- [ ] [Apply the correct labels and milestone](https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#1-product-managers-role) -- [ ] Crosslink the document from the higher-level index -- [ ] Crosslink the document from other subject-related docs -- [ ] Feature moving tiers? Make sure the change is also reflected in [`features.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml) -- [ ] Correctly apply the product [badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) and [tiers](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers) -- [ ] [Port the MR to EE (or backport from CE)](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee): _always recommended, required when the `ee-compat-check` job fails_ +- [ ] Follow the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html). +- [ ] Link docs to and from the higher-level index page, plus other related docs where helpful. +- [ ] Apply the ~Documentation label. ## Review checklist -- [ ] Your team's review (required) -- [ ] PM's review (recommended, but not a blocker) -- [ ] Technical writer's review (required) -- [ ] Merge the EE-MR first, CE-MR afterwards +All reviewers can help ensure accuracy, clarity, completeness, and adherence to the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html). + +**1. Primary Reviewer** + +* [ ] Review by a code reviewer or other selected colleague to confirm accuracy, clarity, and completeness. This can be skipped for minor fixes without substantive content changes. + +**2. Technical Writer** + +* [ ] Optional: Technical writer review. If not requested for this MR, must be scheduled post-merge. To request for this MR, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). + +**3. Maintainer** + +1. [ ] Review by assigned maintainer, who can always request/require the above reviews. Maintainer's review can occur before or after a technical writer review. +1. [ ] Ensure a release milestone is set and that you merge the equivalent EE MR before the CE MR if both exist. +1. [ ] If there has not been a technical writer review, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review). /label ~Documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index e220d61b316..feda5e0835b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,253 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.8.0 (2019-02-22) + +### Security (7 changes, 1 of them is from the community) + +- Sanitize user full name to clean up any URL to prevent mail clients from auto-linking URLs. !2793 +- Update Helm to 2.12.2 to address Helm client vulnerability. !24418 (Takuya Noguchi) +- Use sanitized user status message for user popover. +- Validate bundle files before unpacking them. +- Alias GitHub and BitBucket OAuth2 callback URLs. +- Fixed XSS content in KaTex links. +- Disallows unauthorized users from accessing the pipelines section. + +### Removed (2 changes, 1 of them is from the community) + +- Removed deprecated Redcarpet markdown engine. +- Remove Cancel all jobs button in general jobs list view. (Jordi Llull) + +### Fixed (84 changes, 20 of them are from the community) + +- Fix ambiguous brackets in task lists. !18514 (Jared Deckard <jared.deckard@gmail.com>) +- Fix lost line number when navigating to a specific line in a protected file before authenticating. !19165 (Scott Escue) +- Fix suboptimal handling of checkbox and radio input events causing group general settings submit button to stay disabled after changing its visibility. !23022 +- Fix upcoming milestones filter not including group milestones. !23098 (Heinrich Lee Yu) +- Update runner admin page to make description field larger. !23593 (Sascha Reynolds) +- Fix Bitbucket Server import not allowing personal projects. !23601 +- Fix bug causing repository mirror settings UI to break. !23712 +- Fix foreground color for labels to ensure consistency of label appearance. !23873 (Nathan Friend) +- Resolve In Merge Request diff screen, master is not a hyperlink. !23874 +- Show the correct error page when access is denied. !23932 +- Increase reliability and performance of toggling task items. !23938 +- Modify file restore to rectify tar issue. !24000 +- Fix default visibility_level for new projects. !24120 (Fabian Schneider @fabsrc) +- Footnotes now render properly in markdown. !24168 +- Emoji and cancel button are taller than input in set user status modal. !24173 (Dhiraj Bodicherla) +- Adjusts duplicated line when commenting on unfolded diff lines (in the bottom). !24201 +- Adjust height of "Add list" dropdown in issue boards. !24227 +- Improves restriction of multiple Kubernetes clusters through API. !24251 +- Fix files/blob api endpoints content disposition. !24267 +- Cleanup stale +deleted repo paths on project removal (adjusts project removal bug). !24269 +- Handle regular job dependencies next to parallelized job dependencies. !24273 +- Proper align Projects dropdown on issue boards page. !24277 (Johann Hubert Sonntagbauer) +- Resolve When merging an MR, the squash checkbox isnt always supported. !24296 +- Fix Bitbucket Server importer error handling. !24343 +- Fix syntax highlighting for suggested changes preview. !24358 +- API: Support dots in wiki slugs. !24383 (Robert Schilling) +- Show CI artifact file size with 3 significant digits on 'browse job artifacts' page. !24387 +- API: Support username with dots. !24395 (Robert Schilling) +- API: Fix default_branch_protection admin setting. !24398 (Robert Schilling) +- Remove unwanted margin above suggested changes. !24419 +- Prevent checking protected_ref? for ambiguous refs. !24437 +- Update metrics environment dropdown to show complete option set. !24441 +- Fix empty labels of CI builds for gitlab-pages on pipeline page. !24451 +- Do not run spam checks on confidential issues. !24453 +- Upgrade KaTeX to version 0.10.0. !24478 (Andrew Harmon) +- Avoid overwriting default jaeger values with nil. !24482 +- Display SAML failure messages instead of expecting CSRF token. !24509 +- Adjust vertical alignment for project visibility icons. !24511 (Martin Hobert) +- Load initUserInternalRegexPlaceholder only when required. !24522 +- Hashed Storage: `AfterRenameService` was receiving the wrong `old_path` under some circunstances. !24526 +- Resolve Runners IPv6 address overlaps other values. !24531 +- Fix 404s with snippet uploads in object storage. !24550 +- Fixed oversized custom project notification selector dropdown. !24557 +- Allow users with full private access to read private personal snippets. !24560 +- Resolve Pipeline stages job action button icon is not aligned. !24577 +- Fix cluster page non-interactive on form validation error. !24583 +- Fix 404s for snippet uploads when relative URL root used. !24588 +- Fix markdown table border. !24601 +- Fix CSS grid on a new Project/Group Milestone. !24614 (Takuya Noguchi) +- Prevent unload when Recaptcha is open. !24625 +- Clean up unicorn sampler metric labels. !24626 (bjk-gitlab) +- Support bamboo api polymorphism. !24680 (Alex Lossent) +- Ensure Cert Manager works with Auto DevOps URLs greater than 64 bytes. !24683 +- Fix failed LDAP logins when nil user_id present. !24749 +- fix display comment avatars issue in IE 11. !24777 (Gokhan Apaydin) +- Fix template labels not being created on new projects. !24803 +- Fix cluster installation processing spinner. !24814 +- Append prioritized label before pagination. !24815 +- Resolve UI bug adding group members with lower permissions. !24820 +- Make `ActionController::Parameters` serializable for sidekiq jobs. !24864 +- Fix Jira Service password validation on project integration services. !24896 (Daniel Juarez) +- Fix potential Addressable::URI::InvalidURIError. !24908 +- Update Workhorse to v8.2.0. !24909 +- Encode Content-Disposition filenames. !24919 +- Avoid race conditions when creating GpgSignature. !24939 +- Create the source branch for a GitHub import. !25064 +- Fix suggested changes syntax highlighting. !25116 +- Fix counts in milestones dashboard. !25230 +- Fixes incorrect TLD validation errors for Kubernetes cluster domain. !25262 +- Fix 403 errors when adding an assignee list in project boards. !25263 +- Prevent Auto DevOps from trying to deploy without a domain name. !25308 +- Fix uninitialized constant with GitLab Pages. +- Increase line height of project summaries. (gfyoung) +- Remove extra space between MR tab bar and sticky file headers. +- Correct spacing for comparison page. +- Update CI YAML param table with include. +- Return bottom border on MR Tabs. +- Fixes z-index and margins of archived alert in job page. +- Fixes archived sticky top bar without perfomance bar. +- Fixed rebase button not showing in merge request widget. +- Fixed double tooltips on note awards buttons. +- Allow suggestions to be copied and pasted as GFM. +- Fix bug that caused Suggestion Markdown toolbar button to insert snippet with leading +/-/<space>. +- Moved primary button for labels to follow the design patterns used on rest of the site. (Martin Hobert) + +### Changed (37 changes, 11 of them are from the community) + +- Change spawning of tooltips to be top by default. !21223 +- Standardize filter value capitlization in filter bar in both issues and boards pages. !23846 (obahareth) +- Refresh group overview to match project overview. !23866 +- Build number does not need to be tweaked anymore for the TeamCity integration to work properly. !23898 +- Added empty project illustration and updated text to user profile overview. !23973 (Fernando Arias) +- Modified Knative list view to provide more details. !24072 (Chris Baumbauer) +- Move cancel & new issue button on job page. !24074 +- Make issuable empty states actionable. !24077 +- Fix code search when text is larger than max gRPC message size. !24111 +- Update string structure for available group runners. !24187 (George Tsiolis) +- Remove multilingual translation from the word "in" in the job details sidebar. !24192 (Nathan Friend) +- Fix duplicate project disk path in BackfillLegacyProjectRepositories. !24213 +- Ensured links to a comment or system note anchor resolves to the right note if a user has a discussion filter. !24228 +- Remove expansion hover animation from pipeline status icon buttons. !24268 (Nathan Friend) +- Redesigned related merge requests in issue page. !24270 +- Return the maximum group access level in the projects API. !24403 +- Update project topics styling to use badges design. !24415 +- Display "commented" only for commit discussions on merge requests. !24427 +- Upgrade js-regex gem to version 3.1. !24433 (rroger) +- Prevent Sidekiq arguments over 10 KB in size from being logged to JSON. !24493 +- Added Avatar in the settings sidebar. !24515 (Yoginth) +- Refresh empty states for profile page tabs. !24549 +- remove red/green colors from diff view of no-color syntax theme. !24582 (khm) +- Get remote IP address of runner. !24624 +- Update last_activity_on for Users on some main GET endpoints. !24642 +- Update metrics dashboard graph design. !24653 +- Update to GitLab SVG icon from Font Awesome in profile for location and work. !24671 (Yoginth) +- Add template for Android with Fastlane. !24722 +- Display timestamps to messages printed by gitlab:backup:restore rake tasks. (Will Chandler) +- Show MR statistics in diff comparisons. +- Make possible to toggle file tree while scrolling through diffs. +- Use delete instead of remove when referring to `git branch -D`. +- Add folder header to files in merge request tree list. +- Added fuzzy file finder to merge requests. +- Collapse directory structure in merge request file tree. +- Adds skeleton loading to releases page. +- Support multiple outputs in jupyter notebooks. + +### Performance (8 changes, 1 of them is from the community) + +- Remove unused button classes `btn-create` and `comment-btn`. !23232 (George Tsiolis) +- [API] Omit `X-Total` and `X-Total-Pages` headers when items count is more than 10,000. !23931 +- Improve efficiency of GitHub importer by reducing amount of locks needed. !24102 +- Improve milestone queries using subqueries instead of separate queries for ids. !24325 +- Efficiently remove expired artifacts in `ExpireBuildArtifactsWorker`. !24450 +- Eliminate N+1 queries in /api/groups/:id. !24513 +- Use deployment relation to get an environment name. !24890 +- Do not reload daemon if configuration file of pages does not change. + +### Added (35 changes, 18 of them are from the community) + +- Add badge count to projects. !18425 (George Tsiolis) +- API: Add support for group labels. !21368 (Robert Schilling) +- Add setting for first day of the week. !22755 (Fabian Schneider @fabsrc) +- Pages for subgroups. !23505 +- Add support for customer provided encryption keys for Amazon S3 remote backups. !23797 (Pepijn Van Eeckhoudt) +- Add Knative detailed view. !23863 (Chris Baumbauer) +- Add group full path to project's shared_with_groups. !24052 (Mathieu Parent) +- Added feature to specify a custom Auto DevOps chart repository. !24162 (walkafwalka) +- Add flat-square badge style. !24172 (Fabian Schneider @fabsrc) +- Display last activity and created at datetimes for users. !24181 +- Allow setting of feature gates per project. !24184 +- Save issues/merge request sorting options to backend. !24198 +- Added support for custom hosts/domains to Auto DevOps. !24248 (walkafwalka) +- Adds milestone search. !24265 (Jacopo Beschi @jacopo-beschi) +- Allow merge request diffs to be placed into an object store. !24276 +- Add Container Registry API with cleanup function. !24303 +- GitLab now supports the profile and email scopes from OpenID Connect. !24335 (Goten Xiao) +- Add 'in' filter that modifies scope of 'search' filter to issues and merge requests API. !24350 (Hiroyuki Sato) +- Add `with_programming_language` filter for projects to API. !24377 (Dylan MacKenzie) +- API: Support searching for tags. !24385 (Robert Schilling) +- Document graphicsmagick installation for source installation. !24404 (Alexis Reigel) +- Redirect GET projects/:id to project page. !24467 +- Indicate on Issue Status if an Issue was Moved. !24470 +- Redeploy Auto DevOps deployment on variable updates. !24498 (walkafwalka) +- Don't create new merge request pipeline without commits. !24503 (Hiroyuki Sato) +- Add GitLab Pages predefined CI variables 'CI_PAGES_DOMAIN' and 'CI_PAGES_URL'. !24504 (Adrian Moisey) +- Moves domain setting from Auto DevOps to Cluster's page. !24580 +- API allows setting the squash commit message when squashing a merge request. !24784 +- Added ability to upgrade cluster applications. !24789 +- Add argument iids for issues in GraphQL. !24802 +- Add repositories count to usage ping data. !24823 +- Add support for extensionless pages URLs. !24876 +- Add templates for most popular Pages templates. !24906 +- Introduce Internal API for searching environment names. !24923 +- Allow admins to invalidate markdown texts by setting local markdown version. + +### Other (50 changes, 18 of them are from the community) + +- Externalize strings from `/app/views/projects/project_members`. !23227 (Tao Wang) +- Add CSS & JS global flags to represent browser and platform. !24017 +- Fix deprecation: Passing an argument to force an association to reload is now deprecated. !24136 (Jasper Maes) +- Cleanup legacy artifact background migration. !24144 +- Bump kubectl in Auto DevOps to 1.11.6. !24176 +- Conditionally initialize the global opentracing tracer. !24186 +- Remove horizontal whitespace on user profile overview on small breakpoints. !24189 +- Bump nginx-ingress chart to 1.1.2. !24203 +- Use monospace font for registry table tag id and tag name. !24205 +- Rename project tags to project topics. !24219 +- Add uniqueness validation to url column in Releases::Link model. !24223 +- Update sidekiq-cron to 1.0.4 and use fugit to replace rufus-scheduler to parse cron syntax. !24235 +- Adds inter-service OpenTracing propagation. !24239 +- Fixes Auto DevOps title on CI/CD admin settings. !24249 +- Upgrade kubeclient to 4.2.2 and swap out monkey-patch to disallow redirects. !24284 +- i18n: externalize strings from 'app/views/search'. !24297 (Tao Wang) +- Fix several ActionController::Parameters deprecations. !24332 (Jasper Maes) +- Remove all `$theme-gray-{weight}` variables in favor of `$gray-{weight}`. !24333 (George Tsiolis) +- Update gitlab-styles to 2.5.1. !24336 (Jasper Maes) +- Modifies environment scope UI on cluster page. !24376 +- Extract process_name from GitLab::Sentry. !24422 +- Upgrade Gitaly to 1.13.0. !24429 +- Actually set raise_on_unfiltered_parameters to true. !24443 (Jasper Maes) +- Refactored NoteableDiscussion by extracting ResolveDiscussionButton. !24505 (Martin Hobert) +- Extracted JumpToNextDiscussionButton to its own component. !24506 (Martin Hobert) +- Extracted ReplyPlaceholder to its own component. !24507 (Martin Hobert) +- Block emojis and symbol characters from users full names. !24523 +- Update GitLab Runner Helm Chart to 0.1.45. !24564 +- Updated docs for fields in pushing mirror from GitLab to GitHub. !24566 (Joseph Yu) +- Upgrade gitlab-workhorse to 8.1.0. !24571 +- Externalize strings from `/app/views/sent_notifications`. !24576 (George Tsiolis) +- Adds tracing support for ActiveRecord notifications. !24604 +- Externalize strings from `/app/views/projects/ci`. !24617 (George Tsiolis) +- Move permission check of manual actions of deployments. !24660 +- Externalize strings from `/app/views/clusters`. !24666 (George Tsiolis) +- Update UI for admin appearance settings. !24685 +- Externalize strings from `/app/views/projects/pages_domains`. !24723 (George Tsiolis) +- Externalize strings from `/app/views/projects/milestones`. !24726 (George Tsiolis) +- Add OpenTracing instrumentation for Action View Render events. !24728 +- Expose version for each application in cluster_status JSON endpoint. !24791 +- Externalize strings from `/app/views/instance_statistics`. !24809 (George Tsiolis) +- Update cluster application version on updated and installed status. !24810 +- Project list UI improvements. !24855 +- Externalize strings from `/app/views/email_rejection_mailer`. !24869 (George Tsiolis) +- Update Gitaly to v1.17.0. !24873 +- Update Workhorse to v8.3.0. !24959 +- Upgrade gitaly to 1.18.0. !24981 +- Update Workhorse to v8.3.1. +- Upgraded Codesandbox smooshpack package. +- Creates mixin to reduce code duplication between CE and EE in graph component. + + ## 11.7.5 (2019-02-06) ### Fixed (8 changes) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 39893559155..3500250a4b0 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.20.0 +1.21.0 diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 917d38ec9f9..acd405b1d62 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -8.4.4 +8.6.0 @@ -184,7 +184,7 @@ gem 're2', '~> 1.1.1' # Misc -gem 'version_sorter', '~> 2.1.0' +gem 'version_sorter', '~> 2.2.4' # Export Ruby Regex to Javascript gem 'js_regex', '~> 3.1' @@ -380,7 +380,7 @@ group :test do gem 'shoulda-matchers', '~> 3.1.2', require: false gem 'email_spec', '~> 2.2.0' gem 'json-schema', '~> 2.8.0' - gem 'webmock', '~> 2.3.2' + gem 'webmock', '~> 3.5.1' gem 'rails-controller-testing' gem 'sham_rack', '~> 1.3.6' gem 'concurrent-ruby', '~> 1.1' @@ -419,7 +419,8 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 1.10.0', require: 'gitaly' +gem 'gitaly-proto', '~> 1.12.0', require: 'gitaly' + gem 'grpc', '~> 1.15.0' gem 'google-protobuf', '~> 3.6' diff --git a/Gemfile.lock b/Gemfile.lock index 7caeba15809..59e152c27fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,7 +278,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (1.10.0) + gitaly-proto (1.12.0) grpc (~> 1.0) github-markup (1.7.0) gitlab-default_value_for (3.1.1) @@ -357,7 +357,7 @@ GEM thor tilt hangouts-chat (0.0.5) - hashdiff (0.3.4) + hashdiff (0.3.8) hashie (3.5.7) hashie-forbidden_attributes (0.1.1) hashie (>= 3.0) @@ -916,7 +916,7 @@ GEM validates_hostname (1.0.6) activerecord (>= 3.0) activesupport (>= 3.0) - version_sorter (2.1.0) + version_sorter (2.2.4) virtus (1.0.5) axiom-types (~> 0.1) coercible (~> 1.0) @@ -925,7 +925,7 @@ GEM vmstat (2.3.0) warden (1.2.7) rack (>= 1.0) - webmock (2.3.2) + webmock (3.5.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff @@ -1017,7 +1017,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 1.10.0) + gitaly-proto (~> 1.12.0) github-markup (~> 1.7.0) gitlab-default_value_for (~> 3.1.1) gitlab-markup (~> 1.6.5) @@ -1172,10 +1172,10 @@ DEPENDENCIES unicorn (~> 5.4.1) unicorn-worker-killer (~> 0.4.4) validates_hostname (~> 1.0.6) - version_sorter (~> 2.1.0) + version_sorter (~> 2.2.4) virtus (~> 1.0.1) vmstat (~> 2.3.0) - webmock (~> 2.3.2) + webmock (~> 3.5.1) webpack-rails (~> 0.9.10) wikicloth (= 0.8.1) diff --git a/PROCESS.md b/PROCESS.md index 7c3c826f921..1f99cebe081 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -168,8 +168,12 @@ on behalf of the community member. Every new feature or change should be shipped with its corresponding documentation in accordance with the -[documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html) -and [structure](https://docs.gitlab.com/ee/development/documentation/structure.html). +[documentation process](https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html) +and [structure](https://docs.gitlab.com/ee/development/documentation/structure.html) guides. +Note that a technical writer will review all changes to documentation. This can occur +in the same MR as the feature code, but [if there is not sufficient time or need, +it can be planned via a follow-up issue for doc review](https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#1-product-managers-role), +and another MR, if needed. Regardless, complete docs must be merged with code by the freeze. #### What happens if these deadlines are missed? @@ -198,8 +202,6 @@ and to prevent any last minute surprises. Merge requests should still be complete, following the [definition of done][done]. -#### Feature merge requests - If a merge request is not ready, but the developers and Product Manager responsible for the feature think it is essential that it is in the release, they can [ask for an exception](#asking-for-an-exception) in advance. This is @@ -214,34 +216,17 @@ information, see [Automatic CE->EE merge][automatic_ce_ee_merge] and [Guidelines for implementing Enterprise Edition features][ee_features]. -#### Documentation merge requests - -Documentation is part of the product and must be shipped with the feature. - -The single exception for the feature freeze is documentation, and it can -be left to be **merged up to the 14th** if: - -* There is a follow-up issue to add documentation. -* It is assigned to the developer writing documentation for this feature, and they - are aware of it. -* It is in the correct milestone, with the labels ~Documentation, ~Deliverable, -~missed-deliverable, and "pick into X.Y" applied. -* It must be reviewed and approved by a technical writer. - -For more information read the process for -[documentation shipped late](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late). - ### After the 7th Once the stable branch is frozen, the only MRs that can be cherry-picked into the stable branch are: * Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing bugs](#managing-bugs) section. -* Fixes for security issues -* Fixes or improvements to automated QA scenarios -* [Documentation updates](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late) for changes in the same release -* New or updated translations (as long as they do not touch application code) -* Changes that are behind a feature flag and have the ~"feature flag" label +* Fixes for security issues. +* Fixes or improvements to automated QA scenarios. +* [Documentation improvements](https://docs.gitlab.com/ee/development/documentation/workflow.html) for feature changes made in the same release, though initial docs for these features should have already been merged by the freeze, as required. +* New or updated translations (as long as they do not touch application code). +* Changes that are behind a feature flag and have the ~"feature flag" label. During the feature freeze all merge requests that are meant to go into the upcoming release should have the correct milestone assigned _and_ the @@ -1 +1 @@ -11.8.0-pre +11.9.0-pre diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue index 85a15b38de1..df74eb2c2f7 100644 --- a/app/assets/javascripts/badges/components/badge_form.vue +++ b/app/assets/javascripts/badges/components/badge_form.vue @@ -90,7 +90,7 @@ export default { }, badgeImageUrlExample() { const exampleUrl = - 'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/badge.svg'; + 'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/pipeline.svg'; return sprintf(s__('Badges|e.g. %{exampleUrl}'), { exampleUrl, }); diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 8f47931d14a..1fc2b7fe859 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -5,6 +5,7 @@ import { __ } from '~/locale'; import createFlash from '~/flash'; import { GlLoadingIcon } from '@gitlab/ui'; import PanelResizer from '~/vue_shared/components/panel_resizer.vue'; +import Mousetrap from 'mousetrap'; import eventHub from '../../notes/event_hub'; import CompareVersions from './compare_versions.vue'; import DiffFile from './diff_file.vue'; @@ -87,7 +88,7 @@ export default { emailPatchPath: state => state.diffs.emailPatchPath, }), ...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']), - ...mapGetters('diffs', ['isParallelView']), + ...mapGetters('diffs', ['isParallelView', 'currentDiffIndex']), ...mapGetters(['isNotesFetched', 'getNoteableData']), targetBranch() { return { @@ -149,6 +150,7 @@ export default { }, beforeDestroy() { eventHub.$off('fetchDiffData', this.fetchData); + this.removeEventListeners(); }, methods: { ...mapActions(['startTaskList']), @@ -159,6 +161,7 @@ export default { 'assignDiscussionsToDiff', 'setHighlightedRow', 'cacheTreeListWidth', + 'scrollToFile', ]), fetchData() { this.fetchDiffFiles() @@ -197,7 +200,35 @@ export default { this.$nextTick(() => { window.mrTabs.resetViewContainer(); window.mrTabs.expandViewContainer(this.showTreeList); + this.setEventListeners(); }); + } else { + this.removeEventListeners(); + } + }, + setEventListeners() { + Mousetrap.bind(['[', 'k', ']', 'j'], (e, combo) => { + switch (combo) { + case '[': + case 'k': + this.jumpToFile(-1); + break; + case ']': + case 'j': + this.jumpToFile(+1); + break; + default: + break; + } + }); + }, + removeEventListeners() { + Mousetrap.unbind(['[', 'k', ']', 'j']); + }, + jumpToFile(step) { + const targetIndex = this.currentDiffIndex + step; + if (targetIndex >= 0 && targetIndex < this.diffFiles.length) { + this.scrollToFile(this.diffFiles[targetIndex].file_path); } }, }, diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 82ff2e3be76..c40775c3259 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -52,7 +52,9 @@ export const fetchDiffFiles = ({ state, commit }) => { }; export const setHighlightedRow = ({ commit }, lineCode) => { + const fileHash = lineCode.split('_')[0]; commit(types.SET_HIGHLIGHTED_ROW, lineCode); + commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash); }; // This is adding line discussions to the actual lines in the diff tree @@ -262,8 +264,6 @@ export const scrollToFile = ({ state, commit }, path) => { document.location.hash = fileHash; commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash); - - setTimeout(() => commit(types.UPDATE_CURRENT_DIFF_FILE_ID, ''), 1000); }; export const toggleShowTreeList = ({ commit, state }) => { diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js index 4e7e5306995..bc27e263bff 100644 --- a/app/assets/javascripts/diffs/store/getters.js +++ b/app/assets/javascripts/diffs/store/getters.js @@ -100,5 +100,12 @@ export const diffFilesLength = state => state.diffFiles.length; export const getCommentFormForDiffFile = state => fileHash => state.commentForms.find(form => form.fileHash === fileHash); +/** + * Returns index of a currently selected diff in diffFiles + * @returns {number} + */ +export const currentDiffIndex = state => + Math.max(0, state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId)); + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index e2c304de00a..eef141a07ba 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -3,6 +3,7 @@ * Render environments table. */ import { GlLoadingIcon } from '@gitlab/ui'; +import _ from 'underscore'; import environmentItem from './environment_item.vue'; export default { @@ -24,6 +25,15 @@ export default { default: false, }, }, + computed: { + sortedEnvironments() { + return this.sortEnvironments(this.environments).map(env => + this.shouldRenderFolderContent(env) + ? { ...env, children: this.sortEnvironments(env.children) } + : env, + ); + }, + }, methods: { folderUrl(model) { return `${window.location.pathname}/folders/${model.folderName}`; @@ -31,6 +41,30 @@ export default { shouldRenderFolderContent(env) { return env.isFolder && env.isOpen && env.children && env.children.length > 0; }, + sortEnvironments(environments) { + /* + * The sorting algorithm should sort in the following priorities: + * + * 1. folders first, + * 2. last updated descending, + * 3. by name ascending, + * + * the sorting algorithm must: + * + * 1. Sort by name ascending, + * 2. Reverse (sort by name descending), + * 3. Sort by last deployment ascending, + * 4. Reverse (last deployment descending, name ascending), + * 5. Put folders first. + */ + return _.chain(environments) + .sortBy(env => (env.isFolder ? env.folderName : env.name)) + .reverse() + .sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000')) + .reverse() + .sortBy(env => (env.isFolder ? -1 : 1)) + .value(); + }, }, }; </script> @@ -53,7 +87,7 @@ export default { {{ s__('Environments|Updated') }} </div> </div> - <template v-for="(model, i) in environments" :model="model"> + <template v-for="(model, i) in sortedEnvironments" :model="model"> <div is="environment-item" :key="`environment-item-${i}`" diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index e81a1525df0..9d83840c87c 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -43,7 +43,11 @@ export default { saveData(resp) { this.isLoading = false; - if (_.isEqual(resp.config.params, this.requestData)) { + // Prevent the absence of the nested flag from causing mismatches + const response = this.filterNilValues(resp.config.params); + const request = this.filterNilValues(this.requestData); + + if (_.isEqual(response, request)) { this.store.storeAvailableCount(resp.data.available_count); this.store.storeStoppedCount(resp.data.stopped_count); this.store.storeEnvironments(resp.data.environments); @@ -51,6 +55,10 @@ export default { } }, + filterNilValues(obj) { + return _.omit(obj, value => _.isUndefined(value) || _.isNull(value)); + }, + /** * Handles URL and query parameter changes. * When the user uses the pagination or the tabs, @@ -64,10 +72,9 @@ export default { // fetch new data return this.service .fetchEnvironments(this.requestData) - .then(response => this.successCallback(response)) - .then(() => { - // restart polling - this.poll.restart({ data: this.requestData }); + .then(response => { + this.successCallback(response); + this.poll.enable({ data: this.requestData, response }); }) .catch(() => { this.errorCallback(); diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js index 2e192c958ba..11aec312368 100644 --- a/app/assets/javascripts/error_tracking/store/actions.js +++ b/app/assets/javascripts/error_tracking/store/actions.js @@ -2,7 +2,7 @@ import Service from '../services'; import * as types from './mutation_types'; import createFlash from '~/flash'; import Poll from '~/lib/utils/poll'; -import { __ } from '~/locale'; +import { __, sprintf } from '~/locale'; let eTagPoll; @@ -19,9 +19,17 @@ export function startPolling({ commit }, endpoint) { commit(types.SET_EXTERNAL_URL, data.external_url); commit(types.SET_LOADING, false); }, - errorCallback: () => { + errorCallback: response => { + let errorMessage = ''; + if (response && response.data && response.data.message) { + errorMessage = response.data.message; + } commit(types.SET_LOADING, false); - createFlash(__('Failed to load errors from Sentry')); + createFlash( + sprintf(__(`Failed to load errors from Sentry. Error message: %{errorMessage}`), { + errorMessage, + }), + ); }, }); 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 57ec6603d80..4d05f46ed17 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -96,6 +96,11 @@ export default class FilteredSearchDropdownManager { gl: DropdownNonUser, element: this.container.querySelector('#js-dropdown-wip'), }, + confidential: { + reference: null, + gl: DropdownNonUser, + element: this.container.querySelector('#js-dropdown-confidential'), + }, status: { reference: null, gl: NullDropdown, diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js index b70da240833..48534bdf815 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -72,6 +72,23 @@ export default class FilteredSearchTokenKeys { ); } + addExtraTokensForIssues() { + const confidentialToken = { + key: 'confidential', + type: 'string', + param: '', + symbol: '', + icon: 'eye-slash', + tag: 'Yes or No', + lowercaseValueOnSubmit: true, + uppercaseTokenName: false, + capitalizeTokenValue: true, + }; + + this.tokenKeys.push(confidentialToken); + this.tokenKeysWithAlternative.push(confidentialToken); + } + addExtraTokensForMergeRequests() { const wipToken = { key: 'wip', diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue index 2cbc7c7077b..42d14b65b3a 100644 --- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue +++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue @@ -82,8 +82,14 @@ export default { <li class="frequent-items-list-item-container"> <a :href="webUrl" class="clearfix"> <div class="frequent-items-item-avatar-container"> - <img v-if="hasAvatar" :src="avatarUrl" class="avatar s32" /> - <identicon v-else :entity-id="itemId" :entity-name="itemName" size-class="s32" /> + <img v-if="hasAvatar" :src="avatarUrl" class="avatar rect-avatar s32" /> + <identicon + v-else + :entity-id="itemId" + :entity-name="itemName" + size-class="s32" + class="rect-avatar" + /> </div> <div class="frequent-items-item-metadata-container"> <div :title="itemName" class="frequent-items-item-title" v-html="highlightedItemName"></div> diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 570d3b712e0..c81e754df4c 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -195,11 +195,15 @@ class GfmAutoComplete { title += ` (${m.count})`; } + const GROUP_TYPE = 'Group'; + const autoCompleteAvatar = m.avatar_url || m.username.charAt(0).toUpperCase(); + + const rectAvatarClass = m.type === GROUP_TYPE ? 'rect-avatar' : ''; const imgAvatar = `<img src="${m.avatar_url}" alt="${ m.username - }" class="avatar avatar-inline center s26"/>`; - const txtAvatar = `<div class="avatar center avatar-inline s26">${autoCompleteAvatar}</div>`; + }" class="avatar ${rectAvatarClass} avatar-inline center s26"/>`; + const txtAvatar = `<div class="avatar ${rectAvatarClass} center avatar-inline s26">${autoCompleteAvatar}</div>`; return { username: m.username, diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index 688bd37cc56..d5130cd331d 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -88,7 +88,7 @@ export default { </div> <div :class="{ 'content-loading': group.isChildrenLoading }" - class="avatar-container s24 d-none d-sm-flex" + class="avatar-container rect-avatar s24 d-none d-sm-flex" > <a :href="group.relativePath" class="no-expand"> <img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s24" /> diff --git a/app/assets/javascripts/issuable_suggestions/index.js b/app/assets/javascripts/issuable_suggestions/index.js index 2c80cf1797a..40916c9d27f 100644 --- a/app/assets/javascripts/issuable_suggestions/index.js +++ b/app/assets/javascripts/issuable_suggestions/index.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import defaultClient from '~/lib/graphql'; +import createDefaultClient from '~/lib/graphql'; import App from './components/app.vue'; Vue.use(VueApollo); @@ -10,7 +10,7 @@ export default function() { const issueTitle = document.getElementById('issue_title'); const { projectPath } = el.dataset; const apolloProvider = new VueApollo({ - defaultClient, + defaultClient: createDefaultClient(), }); return new Vue({ diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index 20a0f142d9e..64e4e899f44 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -1,9 +1,11 @@ import ApolloClient from 'apollo-boost'; import csrf from '~/lib/utils/csrf'; -export default new ApolloClient({ - uri: `${gon.relative_url_root}/api/graphql`, - headers: { - [csrf.headerKey]: csrf.token, - }, -}); +export default (clientState = {}) => + new ApolloClient({ + uri: `${gon.relative_url_root}/api/graphql`, + headers: { + [csrf.headerKey]: csrf.token, + }, + clientState, + }); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 29fe460017e..a73cdb73690 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -456,21 +456,6 @@ export const historyPushState = newUrl => { export const parseBoolean = value => (value && value.toString()) === 'true'; /** - * Converts permission provided as strings to booleans. - * - * @param {String} string - * @returns {Boolean} - */ -export const convertPermissionToBoolean = permission => { - if (process.env.NODE_ENV !== 'production') { - // eslint-disable-next-line no-console - console.warn('convertPermissionToBoolean is deprecated! Please use parseBoolean instead.'); - } - - return parseBoolean(permission); -}; - -/** * @callback backOffCallback * @param {Function} next * @param {Function} stop diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index 198711cf427..a900ff34bf5 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -63,6 +63,10 @@ export default class Poll { const headers = normalizeHeaders(response.headers); const pollInterval = parseInt(headers[this.intervalHeader], 10); if (pollInterval > 0 && successCodes.indexOf(response.status) !== -1 && this.canPoll) { + if (this.timeoutID) { + clearTimeout(this.timeoutID); + } + this.timeoutID = setTimeout(() => { this.makeRequest(); }, pollInterval); @@ -101,15 +105,25 @@ export default class Poll { } /** - * Restarts polling after it has been stoped + * Enables polling after it has been stopped */ - restart(options) { - // update data + enable(options) { if (options && options.data) { this.options.data = options.data; } this.canPoll = true; + + if (options && options.response) { + this.checkConditions(options.response); + } + } + + /** + * Restarts polling after it has been stopped and makes a request + */ + restart(options) { + this.enable(options); this.makeRequest(); } } diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue index 14c02db7bcc..9e031b03579 100644 --- a/app/assets/javascripts/monitoring/components/charts/area.vue +++ b/app/assets/javascripts/monitoring/components/charts/area.vue @@ -139,8 +139,7 @@ export default { return this.graphData.queries.map(query => query.label).join(', '); }, yAxisLabel() { - const [query] = this.graphData.queries; - return `${this.graphData.y_label} (${query.unit})`; + return `${this.graphData.y_label}`; }, }, watch: { diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index e03d6e9cd02..31164f74201 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -7,7 +7,9 @@ import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE, DISCUSSION_TAB_LABEL, + DISCUSSION_FILTER_TYPES, } from '../constants'; +import notesEventHub from '../event_hub'; export default { components: { @@ -46,6 +48,7 @@ export default { this.toggleFilters(currentTab); } + notesEventHub.$on('dropdownSelect', this.selectFilter); window.addEventListener('hashchange', this.handleLocationHash); this.handleLocationHash(); }, @@ -53,6 +56,7 @@ export default { this.toggleCommentsForm(); }, destroyed() { + notesEventHub.$off('dropdownSelect', this.selectFilter); window.removeEventListener('hashchange', this.handleLocationHash); }, methods: { @@ -86,12 +90,23 @@ export default { this.setTargetNoteHash(hash); } }, + filterType(value) { + if (value === 0) { + return DISCUSSION_FILTER_TYPES.ALL; + } else if (value === 1) { + return DISCUSSION_FILTER_TYPES.COMMENTS; + } + return DISCUSSION_FILTER_TYPES.HISTORY; + }, }, }; </script> <template> - <div v-if="displayFilters" class="discussion-filter-container d-inline-block align-bottom"> + <div + v-if="displayFilters" + class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom" + > <button id="discussion-filter-dropdown" ref="dropdownToggle" @@ -102,12 +117,17 @@ export default { {{ currentFilter.title }} <icon name="chevron-down" /> </button> <div + ref="dropdownMenu" class="dropdown-menu dropdown-menu-selectable dropdown-menu-right" aria-labelledby="discussion-filter-dropdown" > <div class="dropdown-content"> <ul> - <li v-for="filter in filters" :key="filter.value"> + <li + v-for="filter in filters" + :key="filter.value" + :data-filter-type="filterType(filter.value)" + > <button :class="{ 'is-active': filter.value === currentValue }" class="qa-filter-options" diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue new file mode 100644 index 00000000000..46661e06f6d --- /dev/null +++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue @@ -0,0 +1,52 @@ +<script> +import { GlButton } from '@gitlab/ui'; +import Icon from '~/vue_shared/components/icon.vue'; +import { __, sprintf } from '~/locale'; + +import notesEventHub from '../event_hub'; + +export default { + components: { + GlButton, + Icon, + }, + computed: { + timelineContent() { + return sprintf( + __( + "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options.", + ), + { + startTag: `<b>`, + endTag: `</b>`, + }, + false, + ); + }, + }, + methods: { + selectFilter(value) { + notesEventHub.$emit('dropdownSelect', value); + }, + }, +}; +</script> + +<template> + <li class="timeline-entry note note-wrapper discussion-filter-note js-discussion-filter-note"> + <div class="timeline-icon"> + <icon name="comment" /> + </div> + <div class="timeline-content"> + <div v-html="timelineContent"></div> + <div class="discussion-filter-actions mt-2"> + <gl-button variant="default" @click="selectFilter(0)"> + {{ __('Show all activity') }} + </gl-button> + <gl-button variant="default" @click="selectFilter(1)"> + {{ __('Show comments only') }} + </gl-button> + </div> + </div> + </li> +</template> diff --git a/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue b/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue new file mode 100644 index 00000000000..e413398696a --- /dev/null +++ b/app/assets/javascripts/notes/components/discussion_resolve_with_issue_button.vue @@ -0,0 +1,34 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; +import { GlTooltipDirective, GlButton } from '@gitlab/ui'; + +export default { + name: 'ResolveWithIssueButton', + components: { + Icon, + GlButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + url: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <div class="btn-group" role="group"> + <gl-button + v-gl-tooltip + :href="url" + :title="s__('MergeRequests|Resolve this discussion in a new issue')" + class="new-issue-for-discussion discussion-create-issue-btn" + > + <icon name="issue-new" /> + </gl-button> + </div> +</template> diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 2d6fd8b116f..3894dc8c677 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -25,6 +25,7 @@ import noteable from '../mixins/noteable'; import resolvable from '../mixins/resolvable'; import discussionNavigation from '../mixins/discussion_navigation'; import ReplyPlaceholder from './discussion_reply_placeholder.vue'; +import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue'; import jumpToNextDiscussionButton from './discussion_jump_to_next_button.vue'; import eventHub from '../event_hub'; @@ -44,6 +45,7 @@ export default { ReplyPlaceholder, placeholderNote, placeholderSystemNote, + ResolveWithIssueButton, systemNote, TimelineEntryItem, }, @@ -234,6 +236,9 @@ export default { url: this.discussion.discussion_path, }; }, + resolveWithIssuePath() { + return !this.discussionResolved && this.discussion.resolve_with_issue_path; + }, }, watch: { isReplying() { @@ -487,16 +492,10 @@ Please check your network connection and try again.`; class="btn-group discussion-actions ml-sm-2" role="group" > - <div v-if="!discussionResolved" class="btn-group" role="group"> - <a - v-gl-tooltip - :href="discussion.resolve_with_issue_path" - :title="s__('MergeRequests|Resolve this discussion in a new issue')" - class="new-issue-for-discussion btn btn-default discussion-create-issue-btn" - > - <icon name="issue-new" /> - </a> - </div> + <resolve-with-issue-button + v-if="resolveWithIssuePath" + :url="resolveWithIssuePath" + /> <jump-to-next-discussion-button v-if="shouldShowJumpToNextDiscussion" @onClick="jumpToNextDiscussion" diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index a63571edcea..e2bd59f7631 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -6,6 +6,7 @@ import * as constants from '../constants'; import eventHub from '../event_hub'; import noteableNote from './noteable_note.vue'; import noteableDiscussion from './noteable_discussion.vue'; +import discussionFilterNote from './discussion_filter_note.vue'; import systemNote from '../../vue_shared/components/notes/system_note.vue'; import commentForm from './comment_form.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; @@ -24,6 +25,7 @@ export default { placeholderNote, placeholderSystemNote, skeletonLoadingContainer, + discussionFilterNote, }, props: { noteableData: { @@ -90,8 +92,15 @@ export default { this.fetchNotes(); } }, + allDiscussions() { + if (this.discussonsCount) { + this.discussonsCount.textContent = this.allDiscussions.length; + } + }, }, created() { + this.discussonsCount = document.querySelector('.js-discussions-count'); + this.setNotesData(this.notesData); this.setNoteableData(this.noteableData); this.setUserData(this.userData); @@ -228,6 +237,7 @@ export default { :help-page-path="helpPagePath" /> </template> + <discussion-filter-note v-show="commentsDisabled" /> </ul> <comment-form v-if="!commentsDisabled" :noteable-type="noteableType" /> diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index 78d365fe94b..fba3db8542c 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -24,3 +24,9 @@ export const NOTEABLE_TYPE_MAPPING = { MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE, Epic: EPIC_NOTEABLE_TYPE, }; + +export const DISCUSSION_FILTER_TYPES = { + ALL: 'all', + COMMENTS: 'comments', + HISTORY: 'history', +}; diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 736c6a62610..35d4b034654 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -1,9 +1,11 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; -import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; +import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; document.addEventListener('DOMContentLoaded', () => { + IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); + initFilteredSearch({ page: FILTERED_SEARCH.ISSUES, isGroupDecendent: true, diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index a56c0bb6be8..8bf0c2edc71 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -4,11 +4,13 @@ import IssuableIndex from '~/issuable_index'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import UsersSelect from '~/users_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; -import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; +import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; document.addEventListener('DOMContentLoaded', () => { + IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); + initFilteredSearch({ page: FILTERED_SEARCH.ISSUES, filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index 39cd891c111..636308c5401 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -221,7 +221,7 @@ export default class UserTabs { const monthsAgo = UserTabs.getVisibleCalendarPeriod($calendarWrap); const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath'); const utcOffset = $calendarWrap.data('utcOffset'); - const calendarHint = __('Issues, merge requests, pushes and comments.'); + const calendarHint = __('Issues, merge requests, pushes, and comments.'); $calendarWrap.html(CALENDAR_TEMPLATE); diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index a250e3236f5..244d332f38f 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -78,11 +78,11 @@ export default { <gl-button :class="{ disabled: isActionDisabled(action) }" :disabled="isActionDisabled(action)" - class="js-pipeline-action-link no-btn btn" + class="js-pipeline-action-link no-btn btn d-flex align-items-center justify-content-between flex-wrap" @click="onClickAction(action)" > {{ action.name }} - <span v-if="action.scheduled_at" class="pull-right"> + <span v-if="action.scheduled_at"> <icon name="clock" /> <gl-countdown :end-date-string="action.scheduled_at" /> </span> diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index 32bfa47e5f2..74ca3071364 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -94,8 +94,7 @@ export default { this.isLoading = false; this.successCallback(response); - // restart polling - this.poll.restart({ data: this.requestData }); + this.poll.enable({ data: this.requestData, response }); }) .catch(() => { this.isLoading = false; diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index d65e73a3f9c..6fb25622a05 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -125,6 +125,10 @@ const bindEvents = () => { text: 'Spring', icon: '.template-option .icon-spring', }, + dotnetcore: { + text: '.NET Core', + icon: '.template-option .icon-dotnet', + }, hugo: { text: 'Pages/Hugo', icon: '.template-option .icon-hugo', @@ -145,6 +149,26 @@ const bindEvents = () => { text: 'Pages/Hexo', icon: '.template-option .icon-hexo', }, + nfhugo: { + text: 'Netlify/Hugo', + icon: '.template-option .icon-netlify', + }, + nfjekyll: { + text: 'Netlify/Jekyll', + icon: '.template-option .icon-netlify', + }, + nfplainhtml: { + text: 'Netlify/Plain HTML', + icon: '.template-option .icon-netlify', + }, + nfgitbook: { + text: 'Netlify/GitBook', + icon: '.template-option .icon-netlify', + }, + nfhexo: { + text: 'Netlify/Hexo', + icon: '.template-option .icon-netlify', + }, }; const selectedTemplate = templates[value]; diff --git a/app/assets/javascripts/releases/store/actions.js b/app/assets/javascripts/releases/store/actions.js index baa2251403e..b5c4d54ac33 100644 --- a/app/assets/javascripts/releases/store/actions.js +++ b/app/assets/javascripts/releases/store/actions.js @@ -11,7 +11,7 @@ export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES); /** * Fetches the main endpoint. * Will dispatch requestNamespace action before starting the request. - * Will dispatch receiveNamespaceSuccess if the request is successfull + * Will dispatch receiveNamespaceSuccess if the request is successful * Will dispatch receiveNamesapceError if the request returns an error * * @param {String} projectId diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 2f2a37347af..da0a9483f8e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -54,6 +54,12 @@ export default { deployTimeago() { return this.timeFormated(this.deployment.deployed_at); }, + deploymentExternalUrl() { + if (this.deployment.changes && this.deployment.changes.length === 1) { + return this.deployment.changes[0].external_url; + } + return this.deployment.external_url; + }, hasExternalUrls() { return !!(this.deployment.external_url && this.deployment.external_url_formatted); }, @@ -78,7 +84,7 @@ export default { : ''; }, shouldRenderDropdown() { - return this.deployment.changes && this.deployment.changes.length > 0; + return this.deployment.changes && this.deployment.changes.length > 1; }, showMemoryUsage() { return this.hasMetrics && this.showMetrics; @@ -154,12 +160,12 @@ export default { v-if="shouldRenderDropdown" class="js-mr-wigdet-deployment-dropdown inline" :items="deployment.changes" - :main-action-link="deployment.external_url" + :main-action-link="deploymentExternalUrl" filter-key="path" > <template slot="mainAction" slot-scope="slotProps"> <review-app-link - :link="deployment.external_url" + :link="deploymentExternalUrl" :css-class="`deploy-link js-deploy-url inline ${slotProps.className}`" /> </template> @@ -183,7 +189,7 @@ export default { </filtered-search-dropdown> <review-app-link v-else - :link="deployment.external_url" + :link="deploymentExternalUrl" css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inlin" /> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index abbbe19c5ef..57c4dfbe3b7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -315,7 +315,7 @@ export default { :endpoint="mr.testResultsPath" /> - <div class="mr-widget-section p-0"> + <div class="mr-widget-section"> <component :is="componentName" :mr="mr" :service="service" /> <section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links"> diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/default.vue b/app/assets/javascripts/vue_shared/components/project_avatar/default.vue index b399c232937..881b5059d2a 100644 --- a/app/assets/javascripts/vue_shared/components/project_avatar/default.vue +++ b/app/assets/javascripts/vue_shared/components/project_avatar/default.vue @@ -26,7 +26,7 @@ export default { </script> <template> - <span :class="sizeClass" class="avatar-container project-avatar"> + <span :class="sizeClass" class="avatar-container rect-avatar project-avatar"> <project-avatar-image v-if="project.avatar_url" :link-href="project.path" @@ -34,6 +34,12 @@ export default { :img-alt="project.name" :img-size="size" /> - <identicon v-else :entity-id="project.id" :entity-name="project.name" :size-class="sizeClass" /> + <identicon + v-else + :entity-id="project.id" + :entity-name="project.name" + :size-class="sizeClass" + class="rect-avatar" + /> </span> </template> diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue index 19c5da0461a..3074ea859cc 100644 --- a/app/assets/javascripts/vue_shared/components/select2_select.vue +++ b/app/assets/javascripts/vue_shared/components/select2_select.vue @@ -1,5 +1,6 @@ <script> import $ from 'jquery'; +import 'select2/select2'; export default { name: 'Select2Select', diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue index 2a34b4630f2..9f38c2e4b9e 100644 --- a/app/assets/javascripts/vue_shared/components/table_pagination.vue +++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue @@ -149,9 +149,9 @@ export default { }" class="page-item" > - <a class="page-link" @click.prevent="changePage(item.title, item.disabled)"> + <button type="button" class="page-link" @click="changePage(item.title, item.disabled)"> {{ item.title }} - </a> + </button> </li> </ul> </div> diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index bdf20866197..83ad8766cb5 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -40,16 +40,6 @@ @import "components/**/*"; /* - * Code highlight - */ -@import "highlight/dark"; -@import "highlight/monokai"; -@import "highlight/solarized_dark"; -@import "highlight/solarized_light"; -@import "highlight/white"; -@import "highlight/none"; - -/* * Styles for JS behaviors. */ @import "behaviors"; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 62d471bc30c..555ea276c6c 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -65,3 +65,4 @@ @import 'framework/terms'; @import 'framework/read_more'; @import 'framework/flex_grid'; +@import 'framework/system_messages'; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index e132aa4c216..bfd3d776bd4 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -124,6 +124,30 @@ &.s64 { min-width: 64px; min-height: 64px; } } +.rect-avatar { + border-radius: $border-radius-small; + &.s16 { border-radius: $border-radius-small; } + &.s18 { border-radius: $border-radius-small; } + &.s19 { border-radius: $border-radius-small; } + &.s20 { border-radius: $border-radius-small; } + &.s24 { border-radius: $border-radius-default; } + &.s26 { border-radius: $border-radius-default; } + &.s32 { border-radius: $border-radius-default; } + &.s36 { border-radius: $border-radius-default; } + &.s40 { border-radius: $border-radius-default; } + &.s46 { border-radius: $border-radius-default; } + &.s48 { border-radius: $border-radius-large; } + &.s60 { border-radius: $border-radius-large; } + &.s64 { border-radius: $border-radius-large; } + &.s70 { border-radius: $border-radius-large; } + &.s90 { border-radius: $border-radius-large; } + &.s96 { border-radius: $border-radius-large; } + &.s100 { border-radius: $border-radius-large; } + &.s110 { border-radius: $border-radius-large; } + &.s140 { border-radius: $border-radius-large; } + &.s160 { border-radius: $border-radius-large; } +} + .avatar-counter { background-color: $gray-darkest; color: $white-light; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index c1f2f5f8c6a..b09e44e052a 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -52,6 +52,11 @@ word-break: break-all; } +.text-underline, +.text-underline:hover { + text-decoration: underline; +} + .hint { font-style: italic; color: $gl-gray-400; } .light { color: $gl-text-color; } @@ -458,3 +463,7 @@ img.emoji { background-color: $gray-600; } } + +.cursor-pointer { + cursor: pointer; +} diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 961de8402ef..d6c4e68f68f 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -149,14 +149,6 @@ margin: 10px 0; } - // Border around images in issue and MR comments. - img:not(.emoji) { - border: 1px solid $white-normal; - padding: 5px; - margin: 5px 0; - // Ensure that image does not exceed viewport - max-height: calc(100vh - 100px); - } table:not(.js-syntax-highlight) { @include markdown-table; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index b9d0c0d4d96..3b0869e31a9 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -113,11 +113,6 @@ } } -@mixin dark-diff-match-line { - color: $dark-diff-match-bg; - background: $dark-diff-match-color; -} - @mixin webkit-prefix($property, $value) { #{'-webkit-' + $property}: $value; #{$property}: $value; diff --git a/app/assets/stylesheets/framework/system_messages.scss b/app/assets/stylesheets/framework/system_messages.scss new file mode 100644 index 00000000000..3d66136938f --- /dev/null +++ b/app/assets/stylesheets/framework/system_messages.scss @@ -0,0 +1,110 @@ +.header-message, +.footer-message { + padding: 0 15px; + border: 1px solid transparent; + border-radius: 0; + position: fixed; + left: 0; + width: 100%; + text-align: center; + margin: 0; + z-index: 1000; + + p { + @include str-truncated(100%); + margin-top: 0; + margin-bottom: 0; + } +} + +.header-message { + top: 0; + height: $system-header-height; + line-height: $system-header-height; +} + +.footer-message { + bottom: 0; + height: $system-footer-height; + line-height: $system-footer-height; +} + +.with-performance-bar { + .header-message { + top: $performance-bar-height; + } +} + +// System Header +.with-system-header { + // main navigation + // login page + .navbar-gitlab, + .fixed-top { + top: $system-header-height; + } + + // left sidebar eg: project page + // right sidebar eg: MR page + .nav-sidebar, + .right-sidebar { + top: $system-header-height + $header-height; + } + + .content-wrapper { + margin-top: $system-header-height + $header-height; + } + + // Performance Bar + // System Header + &.with-performance-bar { + // main navigation + header.navbar-gitlab { + top: $performance-bar-height + $system-header-height; + } + + .layout-page { + margin-top: $header-height + $performance-bar-height + $system-header-height; + } + + // left sidebar eg: project page + // right sidebar eg: MR page + .nav-sidebar, + .right-sidebar { + top: $header-height + $performance-bar-height + $system-header-height; + } + } +} + +// System Footer +.with-system-footer { + // left sidebar eg: project page + // right sidebar eg: mr page + .nav-sidebar, + .right-sidebar, + // navless pages' footer eg: login page + // navless pages' footer border eg: login page + &.devise-layout-html body .footer-container, + &.devise-layout-html body hr.footer-fixed { + bottom: $system-footer-height; + } +} + +.fullscreen-layout { + .header-message, + .footer-message { + position: static; + top: auto; + bottom: auto; + } + + .content-wrapper { + .with-system-header & { + margin-top: 0; + } + + .with-system-footer & { + margin-top: 0; + } + } +} diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index bf85acdc0d6..1b36c1f4862 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -371,6 +371,16 @@ code { .md:not(.use-csslab) { @include md-typography; + + &:not(.wiki) { + img:not(.emoji) { + border: 1px solid $white-normal; + padding: 5px; + margin: 5px 0; + // Ensure that image does not exceed viewport + max-height: calc(100vh - 100px); + } + } } /** diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index dc1a73ed923..25b272ab3a9 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -265,6 +265,7 @@ $container-text-max-width: 540px; $gl-avatar-size: 40px; $border-radius-default: 4px; $border-radius-small: 2px; +$border-radius-large: 8px; $default-icon-size: 18px; $layout-link-gray: #7e7c7c; $btn-side-margin: 10px; @@ -276,6 +277,8 @@ $general-hover-transition-duration: 100ms; $general-hover-transition-curve: linear; $highlight-changes-color: rgb(235, 255, 232); $performance-bar-height: 35px; +$system-header-height: 35px; +$system-footer-height: $system-header-height; $flash-height: 52px; $context-header-height: 60px; $breadcrumb-min-height: 48px; diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss index 1dfe2a69a2f..9b52493fd4d 100644 --- a/app/assets/stylesheets/framework/variables_overrides.scss +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -41,5 +41,7 @@ $spacers: ( 2: ($spacer), 3: ($spacer * 2), 4: ($spacer * 3), - 5: ($spacer * 4) + 5: ($spacer * 4), + 6: ($spacer * 8) ); +$pagination-color: $gl-text-color; diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss new file mode 100644 index 00000000000..2b0794759d5 --- /dev/null +++ b/app/assets/stylesheets/highlight/common.scss @@ -0,0 +1,18 @@ +@import "../framework/variables"; + +@mixin diff-background($background, $idiff, $border) { + background: $background; + + &.line_content span.idiff { + background: $idiff; + } + + &.diff-line-num { + border-color: $border; + } +} + +@mixin dark-diff-match-line { + color: $dark-diff-match-bg; + background: $dark-diff-match-color; +} diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss index ca9a2a673f5..16893dd047e 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/themes/dark.scss @@ -1,5 +1,7 @@ /* https://github.com/MozMorris/tomorrow-pygments */ +@import "../common"; + /* * Dark syntax colors */ diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss index bc3761d1e47..37fe61b925c 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/themes/monokai.scss @@ -1,5 +1,7 @@ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */ +@import "../common"; + /* * Monokai Colors */ diff --git a/app/assets/stylesheets/highlight/none.scss b/app/assets/stylesheets/highlight/themes/none.scss index 4bedb6a8e5b..b4217aac37a 100644 --- a/app/assets/stylesheets/highlight/none.scss +++ b/app/assets/stylesheets/highlight/themes/none.scss @@ -2,7 +2,7 @@ * None Syntax Colors */ - +@import "../common"; @mixin match-line { color: $black-transparent; diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss index de7b9424340..a4e9eda22c9 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss @@ -1,5 +1,7 @@ /* https://gist.github.com/qguv/7936275 */ +@import "../common"; + /* * Solarized dark colors */ diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss index 84a92d0320a..b604d1ccb6c 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss @@ -1,5 +1,7 @@ /* https://gist.github.com/qguv/7936275 */ +@import "../common"; + /* * Solarized light syntax colors */ diff --git a/app/assets/stylesheets/highlight/themes/white.scss b/app/assets/stylesheets/highlight/themes/white.scss new file mode 100644 index 00000000000..7239086f649 --- /dev/null +++ b/app/assets/stylesheets/highlight/themes/white.scss @@ -0,0 +1,3 @@ +.code.white { + @import "../white_base"; +} diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss deleted file mode 100644 index 355c8d223f7..00000000000 --- a/app/assets/stylesheets/highlight/white.scss +++ /dev/null @@ -1,3 +0,0 @@ -.code.white { - @import "white_base"; -} diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss index c636abbdfad..23ec3380ce9 100644 --- a/app/assets/stylesheets/highlight/white_base.scss +++ b/app/assets/stylesheets/highlight/white_base.scss @@ -1,5 +1,7 @@ /* https://github.com/aahan/pygments-github-style */ +@import "./common"; + /* * White Syntax Colors */ diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index ae0768592e0..d001dff7986 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -602,18 +602,6 @@ } } -@mixin diff-background($background, $idiff, $border) { - background: $background; - - &.line_content span.idiff { - background: $idiff; - } - - &.diff-line-num { - border-color: $border; - } -} - .files { .diff-file:last-child { margin-bottom: 0; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 883c856870f..cfd3faab122 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -82,7 +82,6 @@ } .mr-widget-body, -.mr-widget-section, .mr-widget-content, .mr-widget-footer { padding: $gl-padding; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 7e7eff1346a..1198b9ea143 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -4,7 +4,7 @@ $note-form-margin-left: 72px; @mixin vertical-line($left) { &::before { - content: ''; + content: ""; border-left: 2px solid $gray-100; position: absolute; top: 0; @@ -53,12 +53,12 @@ $note-form-margin-left: 72px; &.note-form { margin-left: 0; - @include notes-media('min', map-get($grid-breakpoints, md)) { + @include notes-media("min", map-get($grid-breakpoints, md)) { margin-left: $note-form-margin-left; } .timeline-icon { - @include notes-media('min', map-get($grid-breakpoints, sm)) { + @include notes-media("min", map-get($grid-breakpoints, sm)) { margin-left: -$note-icon-gutter-width; } } @@ -242,7 +242,7 @@ $note-form-margin-left: 72px; } .note-header { - @include notes-media('max', map-get($grid-breakpoints, xs)) { + @include notes-media("max", map-get($grid-breakpoints, xs)) { .inline { display: block; } @@ -303,28 +303,8 @@ $note-form-margin-left: 72px; } } - .timeline-icon { - float: left; - display: flex; - align-items: center; - background-color: $white-light; - width: $system-note-icon-size; - height: $system-note-icon-size; - border: 1px solid $border-color; - border-radius: $system-note-icon-size; - margin: -6px $gl-padding 0 0; - - svg { - width: $system-note-svg-size; - height: $system-note-svg-size; - fill: $gray-darkest; - display: block; - margin: 0 auto; - } - } - .timeline-content { - @include notes-media('min', map-get($grid-breakpoints, sm)) { + @include notes-media("min", map-get($grid-breakpoints, sm)) { margin-left: 30px; } } @@ -368,7 +348,7 @@ $note-form-margin-left: 72px; } &::after { - content: ''; + content: ""; height: 70px; position: absolute; left: $gl-padding-24; @@ -380,6 +360,37 @@ $note-form-margin-left: 72px; } } } + + .system-note, + .discussion-filter-note { + .timeline-icon { + float: left; + display: flex; + align-items: center; + background-color: $white-light; + width: $system-note-icon-size; + height: $system-note-icon-size; + border: 1px solid $border-color; + border-radius: $system-note-icon-size; + margin: -6px $gl-padding 0 0; + + svg { + width: $system-note-svg-size; + height: $system-note-svg-size; + fill: $gray-darkest; + display: block; + margin: 0 auto; + } + } + } + + .discussion-filter-note { + .timeline-icon { + width: $system-note-icon-size + 6; + height: $system-note-icon-size + 6; + margin-top: -8px; + } + } } // Diff code in discussion view @@ -579,7 +590,7 @@ $note-form-margin-left: 72px; .note-headline-light { display: inline; - @include notes-media('max', map-get($grid-breakpoints, xs)) { + @include notes-media("max", map-get($grid-breakpoints, xs)) { display: block; } } @@ -645,7 +656,7 @@ $note-form-margin-left: 72px; margin-left: 10px; color: $gray-darkest; - @include notes-media('max', map-get($grid-breakpoints, sm) - 1) { + @include notes-media("max", map-get($grid-breakpoints, sm) - 1) { float: none; margin-left: 0; } @@ -764,7 +775,7 @@ $note-form-margin-left: 72px; } .line-resolve-all-container { - @include notes-media('min', map-get($grid-breakpoints, sm)) { + @include notes-media("min", map-get($grid-breakpoints, sm)) { margin-right: 0; } @@ -905,7 +916,6 @@ $note-form-margin-left: 72px; } .discussion-filter-container { - .btn > svg { width: $gl-col-padding; height: $gl-col-padding; @@ -927,7 +937,6 @@ $note-form-margin-left: 72px; //This needs to be deleted when Snippet/Commit comments are convered to Vue // See https://gitlab.com/gitlab-org/gitlab-ce/issues/53918#note_117038785 .unstyled-comments { - .discussion-header { padding: $gl-padding; border-bottom: 1px solid $border-color; diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb index e3226c86b0b..2b9cae21da2 100644 --- a/app/controllers/admin/appearances_controller.rb +++ b/app/controllers/admin/appearances_controller.rb @@ -74,6 +74,10 @@ class Admin::AppearancesController < Admin::ApplicationController favicon_cache new_project_guidelines updated_by + header_message + footer_message + message_background_color + message_font_color ] end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 0eae007715a..bfa7c7d0109 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -7,7 +7,7 @@ class Admin::UsersController < Admin::ApplicationController before_action :check_impersonation_availability, only: :impersonate def index - @users = User.order_name_asc.filter(params[:filter]) + @users = User.filter_items(params[:filter]).order_name_asc @users = @users.search_with_secondary_emails(params[:search_query]) if params[:search_query].present? @users = @users.sort_by_attribute(@sort = params[:sort]) @users = @users.page(params[:page]) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index af0b0c64814..b7eb6af6d67 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -43,7 +43,10 @@ class ApplicationController < ActionController::Base :git_import_enabled?, :gitlab_project_import_enabled?, :manifest_import_enabled? + # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security + # concerns due to caching private data. DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store".freeze + DEFAULT_GITLAB_CONTROL_NO_CACHE = "#{DEFAULT_GITLAB_CACHE_CONTROL}, no-cache".freeze rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -235,9 +238,9 @@ class ApplicationController < ActionController::Base end def no_cache_headers - response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" - response.headers["Pragma"] = "no-cache" - response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" + headers['Cache-Control'] = DEFAULT_GITLAB_CONTROL_NO_CACHE + headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility + headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' end def default_headers @@ -247,10 +250,16 @@ class ApplicationController < ActionController::Base headers['X-Content-Type-Options'] = 'nosniff' if current_user - # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security - # concerns due to caching private data. - headers['Cache-Control'] = DEFAULT_GITLAB_CACHE_CONTROL - headers["Pragma"] = "no-cache" # HTTP 1.0 compatibility + headers['Cache-Control'] = default_cache_control + headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility + end + end + + def default_cache_control + if request.xhr? + ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL + else + DEFAULT_GITLAB_CACHE_CONTROL end end diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 3bd91b71d92..68a2a83f0de 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -24,7 +24,7 @@ class Clusters::ClustersController < Clusters::BaseController # Note: We are paginating through an array here but this should OK as: # # In CE, we can have a maximum group nesting depth of 21, so including - # project cluster, we can have max 22 clusters for a group hierachy. + # project cluster, we can have max 22 clusters for a group hierarchy. # In EE (Premium) we can have any number, as multiple clusters are # supported, but the number of clusters are fairly low currently. # diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 07d0bf16d93..c529aabf797 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -91,6 +91,7 @@ module IssuableCollections options = { scope: params[:scope], state: params[:state], + confidential: Gitlab::Utils.to_boolean(params[:confidential]), sort: set_sort_order } diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 5572c3cee2d..57e444319e0 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -123,7 +123,7 @@ module LfsRequest (authentication_abilities || []).include?(capability) end - # Overriden in EE + # Overridden in EE def limit_exceeded? false end diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 0319948a12f..b4fee93713b 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -6,7 +6,6 @@ module NotesActions extend ActiveSupport::Concern included do - prepend_before_action :normalize_create_params, only: [:create] before_action :set_polling_interval_header, only: [:index] before_action :require_noteable!, only: [:index, :create] before_action :authorize_admin_note!, only: [:update, :destroy] @@ -44,12 +43,7 @@ module NotesActions # rubocop:disable Gitlab/ModuleWithInstanceVariables def create - create_params = note_params.merge( - merge_request_diff_head_sha: params[:merge_request_diff_head_sha], - in_reply_to_discussion_id: params[:in_reply_to_discussion_id] - ) - - @note = Notes::CreateService.new(note_project, current_user, create_params).execute + @note = Notes::CreateService.new(note_project, current_user, create_note_params).execute respond_to do |format| format.json do @@ -78,7 +72,7 @@ module NotesActions # rubocop:disable Gitlab/ModuleWithInstanceVariables def update - @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) + @note = Notes::UpdateService.new(project, current_user, update_note_params).execute(note) prepare_notes_for_rendering([@note]) respond_to do |format| @@ -196,24 +190,36 @@ module NotesActions return access_denied! unless can?(current_user, :admin_note, note) end - def note_params + def create_note_params params.require(:note).permit( - :project_id, - :noteable_type, - :noteable_id, - :commit_id, - :noteable, :type, - :note, - :attachment, + :line_code, # LegacyDiffNote + :position # DiffNote + ).tap do |create_params| + create_params.merge!( + params.permit(:merge_request_diff_head_sha, :in_reply_to_discussion_id) + ) - # LegacyDiffNote - :line_code, + # These params are also sent by the client but we need to set these based on + # target_type and target_id because we're checking permissions based on that + create_params[:noteable_type] = params[:target_type].classify + + case params[:target_type] + when 'commit' + create_params[:commit_id] = params[:target_id] + when 'merge_request' + create_params[:noteable_id] = params[:target_id] + # Notes on MergeRequest can have an extra `commit_id` context + create_params[:commit_id] = params.dig(:note, :commit_id) + else + create_params[:noteable_id] = params[:target_id] + end + end + end - # DiffNote - :position - ) + def update_note_params + params.require(:note).permit(:note) end def set_polling_interval_header @@ -248,15 +254,6 @@ module NotesActions DiscussionSerializer.new(project: project, noteable: noteable, current_user: current_user, note_entity: ProjectNoteEntity) end - # Avoids checking permissions in the wrong object - this ensures that the object we checked permissions for - # is the object we're actually creating a note in. - def normalize_create_params - params[:note].try do |note| - note[:noteable_id] = params[:target_id] - note[:noteable_type] = params[:target_type].classify - end - end - def note_project strong_memoize(:note_project) do next nil unless project diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 94002095739..0227af2c266 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -37,6 +37,14 @@ class Profiles::PreferencesController < Profiles::ApplicationController end def preferences_param_names - [:color_scheme_id, :layout, :dashboard, :project_view, :theme_id, :first_day_of_week] + [ + :color_scheme_id, + :layout, + :dashboard, + :project_view, + :theme_id, + :first_day_of_week, + :preferred_language + ] end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 15248d2d08f..b9c52618d4b 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -104,7 +104,6 @@ class ProfilesController < Profiles::ApplicationController :username, :website_url, :organization, - :preferred_language, :private_profile, :include_private_contributions, status: [:emoji, :message] diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 925b6ed9bfd..c80fce513f6 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -45,7 +45,14 @@ class Projects::GraphsController < Projects::ApplicationController end def get_languages - @languages = @project.repository.languages + @languages = + if @project.repository_languages.present? + @project.repository_languages.map do |lang| + { value: lang.share, label: lang.name, color: lang.color, highlight: lang.color } + end + else + @project.repository.languages + end end def fetch_graph diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 5639402a1e9..32cefe54613 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -89,7 +89,11 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap def build_merge_request params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) - @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute + + # Gitaly N+1 issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/58096 + Gitlab::GitalyClient.allow_n_plus_1_calls do + @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute + end end def define_new_vars diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index d0e35bee986..73e629ab7c3 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -5,7 +5,8 @@ class Projects::PagesController < Projects::ApplicationController before_action :require_pages_enabled! before_action :authorize_read_pages!, only: [:show] - before_action :authorize_update_pages!, except: [:show] + before_action :authorize_update_pages!, except: [:show, :destroy] + before_action :authorize_remove_pages!, only: [:destroy] # rubocop: disable CodeReuse/ActiveRecord def show diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb index 091bcb1253d..eee14b0faf4 100644 --- a/app/controllers/snippets/notes_controller.rb +++ b/app/controllers/snippets/notes_controller.rb @@ -26,10 +26,6 @@ class Snippets::NotesController < ApplicationController # rubocop: enable CodeReuse/ActiveRecord alias_method :noteable, :snippet - def note_params - super.merge(noteable_id: params[:snippet_id]) - end - def finder_params params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet') end diff --git a/app/finders/admin/runners_finder.rb b/app/finders/admin/runners_finder.rb index fbb1cfc5c66..8d936b8121c 100644 --- a/app/finders/admin/runners_finder.rb +++ b/app/finders/admin/runners_finder.rb @@ -14,7 +14,7 @@ class Admin::RunnersFinder < UnionFinder sort! paginate! - @runners + @runners.with_tags end def sort_key diff --git a/app/finders/concerns/finder_methods.rb b/app/finders/concerns/finder_methods.rb index 5290313585f..8de3276184d 100644 --- a/app/finders/concerns/finder_methods.rb +++ b/app/finders/concerns/finder_methods.rb @@ -3,13 +3,13 @@ module FinderMethods # rubocop: disable CodeReuse/ActiveRecord def find_by!(*args) - raise_not_found_unless_authorized execute.find_by!(*args) + raise_not_found_unless_authorized execute.reorder(nil).find_by!(*args) end # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord def find_by(*args) - if_authorized execute.find_by(*args) + if_authorized execute.reorder(nil).find_by(*args) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb index 96a36db7ec8..ec340f38450 100644 --- a/app/finders/group_descendants_finder.rb +++ b/app/finders/group_descendants_finder.rb @@ -134,7 +134,7 @@ class GroupDescendantsFinder def subgroups return Group.none unless Group.supports_nested_objects? - # When filtering subgroups, we want to find all matches withing the tree of + # When filtering subgroups, we want to find all matches within the tree of # descendants to show to the user groups = if params[:filter] subgroups_matching_filter diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 23af2e0521c..5870f158690 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -94,6 +94,7 @@ class IssuableFinder items = by_scope(items) items = by_created_at(items) items = by_updated_at(items) + items = by_closed_at(items) items = by_state(items) items = by_group(items) items = by_assignee(items) @@ -353,6 +354,13 @@ class IssuableFinder items end + def by_closed_at(items) + items = items.closed_after(params[:closed_after]) if params[:closed_after].present? + items = items.closed_before(params[:closed_before]) if params[:closed_before].present? + + items + end + # rubocop: disable CodeReuse/ActiveRecord def by_state(items) case params[:state].to_s diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index a0504ca0879..cb44575d6f1 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -69,7 +69,16 @@ class IssuesFinder < IssuableFinder end def filter_items(items) - by_due_date(super) + issues = super + issues = by_due_date(issues) + issues = by_confidential(issues) + issues + end + + def by_confidential(items) + return items if params[:confidential].nil? + + params[:confidential] ? items.confidential_only : items.public_only end def by_due_date(items) diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index fd1b46ba860..b98d8bd1fff 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -9,7 +9,30 @@ module Resolvers argument :iids, [GraphQL::ID_TYPE], required: false, description: 'The list of IIDs of issues, e.g., [1, 2]' - + argument :state, Types::IssuableStateEnum, + required: false, + description: "Current state of Issue" + argument :label_name, GraphQL::STRING_TYPE.to_list_type, + required: false, + description: "Labels applied to the Issue" + argument :created_before, Types::TimeType, + required: false, + description: "Issues created before this date" + argument :created_after, Types::TimeType, + required: false, + description: "Issues created after this date" + argument :updated_before, Types::TimeType, + required: false, + description: "Issues updated before this date" + argument :updated_after, Types::TimeType, + required: false, + description: "Issues updated after this date" + argument :closed_before, Types::TimeType, + required: false, + description: "Issues closed before this date" + argument :closed_after, Types::TimeType, + required: false, + description: "Issues closed after this date" argument :search, GraphQL::STRING_TYPE, required: false argument :sort, Types::Sort, diff --git a/app/graphql/types/issuable_state_enum.rb b/app/graphql/types/issuable_state_enum.rb new file mode 100644 index 00000000000..f2f6d6c6cab --- /dev/null +++ b/app/graphql/types/issuable_state_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + class IssuableStateEnum < BaseEnum + graphql_name 'IssuableState' + description 'State of a GitLab issue or merge request' + + value 'opened' + value 'closed' + value 'locked' + end +end diff --git a/app/graphql/types/issue_state_enum.rb b/app/graphql/types/issue_state_enum.rb new file mode 100644 index 00000000000..6521407fc9d --- /dev/null +++ b/app/graphql/types/issue_state_enum.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Types + class IssueStateEnum < IssuableStateEnum + graphql_name 'IssueState' + description 'State of a GitLab issue' + end +end diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index a8f2f7914a8..5ad3ea52930 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -11,22 +11,20 @@ module Types field :iid, GraphQL::ID_TYPE, null: false field :title, GraphQL::STRING_TYPE, null: false field :description, GraphQL::STRING_TYPE, null: true - field :state, GraphQL::STRING_TYPE, null: false + field :state, IssueStateEnum, null: false field :author, Types::UserType, null: false, - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } do - authorize :read_user - end + resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }, + authorize: :read_user field :assignees, Types::UserType.connection_type, null: true field :labels, Types::LabelType.connection_type, null: true field :milestone, Types::MilestoneType, null: true, - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } do - authorize :read_milestone - end + resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }, + authorize: :read_milestone field :due_date, Types::TimeType, null: true field :confidential, GraphQL::BOOLEAN_TYPE, null: false diff --git a/app/graphql/types/merge_request_state_enum.rb b/app/graphql/types/merge_request_state_enum.rb new file mode 100644 index 00000000000..92f52726ab3 --- /dev/null +++ b/app/graphql/types/merge_request_state_enum.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Types + class MergeRequestStateEnum < IssuableStateEnum + graphql_name 'MergeRequestState' + description 'State of a GitLab merge request' + + value 'merged' + end +end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 7e63d4022b1..1ed27a14e33 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -12,7 +12,7 @@ module Types field :iid, GraphQL::ID_TYPE, null: false field :title, GraphQL::STRING_TYPE, null: false field :description, GraphQL::STRING_TYPE, null: true - field :state, GraphQL::STRING_TYPE, null: true + field :state, MergeRequestStateEnum, null: false field :created_at, Types::TimeType, null: false field :updated_at, Types::TimeType, null: false field :source_project, Types::ProjectType, null: true @@ -48,9 +48,7 @@ module Types field :downvotes, GraphQL::INT_TYPE, null: false field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false - field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline do - authorize :read_pipeline - end + field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline, authorize: :read_pipeline field :pipelines, Types::Ci::PipelineType.connection_type, resolver: Resolvers::MergeRequestPipelinesResolver end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index d25c8c8bd90..3ef0cc5020c 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -69,16 +69,14 @@ module Types field :merge_requests, Types::MergeRequestType.connection_type, null: true, - resolver: Resolvers::MergeRequestsResolver do - authorize :read_merge_request - end + resolver: Resolvers::MergeRequestsResolver, + authorize: :read_merge_request field :merge_request, Types::MergeRequestType, null: true, - resolver: Resolvers::MergeRequestsResolver.single do - authorize :read_merge_request - end + resolver: Resolvers::MergeRequestsResolver.single, + authorize: :read_merge_request field :issues, Types::IssueType.connection_type, diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 7c41716b82a..954bcc0a5a3 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -7,9 +7,8 @@ module Types field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver, - description: "Find a project" do - authorize :read_project - end + description: "Find a project", + authorize: :read_project field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new end diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index 7fbbbb04154..023e44258b7 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -40,4 +40,36 @@ module AppearancesHelper render 'shared/logo_type.svg' end end + + def header_message + return unless current_appearance&.show_header? + + class_names = [] + class_names << 'with-performance-bar' if performance_bar_enabled? + + render_message(:header_message, class_names) + end + + def footer_message + return unless current_appearance&.show_footer? + + render_message(:footer_message) + end + + private + + def render_message(field_sym, class_names = []) + class_names << field_sym.to_s.dasherize + + content_tag :div, class: class_names, style: message_style do + markdown_field(current_appearance, field_sym) + end + end + + def message_style + style = [] + style << "background-color: #{current_appearance.message_background_color};" + style << "color: #{current_appearance.message_font_color}" + style.join + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9efa84b02f0..ffa5719fefb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -214,12 +214,19 @@ module ApplicationHelper class_names = [] class_names << 'issue-boards-page' if current_controller?(:boards) class_names << 'with-performance-bar' if performance_bar_enabled? - + class_names << system_message_class class_names end - # EE feature: System header and footer, unavailable in CE def system_message_class + class_names = [] + + return class_names unless appearance + + class_names << 'with-system-header' if appearance.show_header? + class_names << 'with-system-footer' if appearance.show_footer? + + class_names end # Returns active css class when condition returns true @@ -292,4 +299,10 @@ module ApplicationHelper snippets: snippets_project_autocomplete_sources_path(object) } end + + private + + def appearance + ::Appearance.current + end end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 23d6684a8e6..06beeebe2ba 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -31,12 +31,13 @@ module BlobHelper edit_button_tag(blob, common_classes, _('Edit'), - edit_blob_path(project, ref, path, options), + Feature.enabled?(:web_ide_default) ? ide_edit_path(project, ref, path, options) : edit_blob_path(project, ref, path, options), project, ref) end def ide_edit_button(project = @project, ref = @ref, path = @path, options = {}) + return unless Feature.enabled?(:web_ide_default) return unless blob = readable_blob(options, path, project, ref) edit_button_tag(blob, diff --git a/app/helpers/count_helper.rb b/app/helpers/count_helper.rb index 13839474e1f..62bb2e4da23 100644 --- a/app/helpers/count_helper.rb +++ b/app/helpers/count_helper.rb @@ -13,7 +13,7 @@ module CountHelper # memberships, and deducting 1 for each root of the fork network. # This might be inacurate as the root of the fork network might have been deleted. # - # This makes querying this information a lot more effecient and it should be + # This makes querying this information a lot more efficient and it should be # accurate enough for the instance wide statistics def approximate_fork_count_with_delimiters(count_data) fork_network_count = count_data[ForkNetwork] diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 1a471034972..af28e6fcb93 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -200,7 +200,7 @@ module IssuablesHelper author_output end - output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!')) + output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip prepend-left-4', title: _('1st contribution!')) output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block prepend-left-8") output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none") diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 39f661b5f0c..bd53add80ca 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -227,6 +227,10 @@ module LabelsHelper "#{action} at #{level} level" end + def labels_sorted_by_title(labels) + labels.sort_by(&:title) + end + # Required for Banzai::Filter::LabelReferenceFilter module_function :render_colored_label, :text_color_for_bg, :escape_once end diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index bc1742e8167..eed529f93db 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -62,6 +62,10 @@ module PreferencesHelper Gitlab::ColorSchemes.for_user(current_user).css_class end + def language_choices + Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] } + end + private # Ensure that anyone adding new options updates `DASHBOARD_CHOICES` too diff --git a/app/models/appearance.rb b/app/models/appearance.rb index ff1ecfda684..b9ad676ca47 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -8,12 +8,19 @@ class Appearance < ActiveRecord::Base cache_markdown_field :description cache_markdown_field :new_project_guidelines + cache_markdown_field :header_message, pipeline: :broadcast_message + cache_markdown_field :footer_message, pipeline: :broadcast_message validates :logo, file_size: { maximum: 1.megabyte } validates :header_logo, file_size: { maximum: 1.megabyte } + validates :message_background_color, allow_blank: true, color: true + validates :message_font_color, allow_blank: true, color: true validate :single_appearance_row, on: :create + default_value_for :message_background_color, '#E75E40' + default_value_for :message_font_color, '#FFFFFF' + mount_uploader :logo, AttachmentUploader mount_uploader :header_logo, AttachmentUploader mount_uploader :favicon, FaviconUploader @@ -41,6 +48,14 @@ class Appearance < ActiveRecord::Base logo_system_path(favicon, 'favicon') end + def show_header? + header_message.present? + end + + def show_footer? + footer_message.present? + end + private def logo_system_path(logo, mount_type) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6b2b7e77180..c902e49ee6d 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -46,6 +46,7 @@ module Ci delegate :terminal_specification, to: :runner_session, allow_nil: true delegate :gitlab_deploy_token, to: :project delegate :trigger_short_token, to: :trigger_request, allow_nil: true + delegate :merge_request?, to: :pipeline ## # Since Gitlab 11.5, deployments records started being created right after @@ -441,11 +442,13 @@ module Ci # All variables, including persisted environment variables. # def variables - Gitlab::Ci::Variables::Collection.new - .concat(persisted_variables) - .concat(scoped_variables) - .concat(persisted_environment_variables) - .to_runner_variables + strong_memoize(:variables) do + Gitlab::Ci::Variables::Collection.new + .concat(persisted_variables) + .concat(scoped_variables) + .concat(persisted_environment_variables) + .to_runner_variables + end end ## diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb index da08214963f..33e61cd2111 100644 --- a/app/models/ci/build_trace_chunk.rb +++ b/app/models/ci/build_trace_chunk.rb @@ -18,7 +18,7 @@ module Ci FailedToPersistDataError = Class.new(StandardError) # Note: The ordering of this enum is related to the precedence of persist store. - # The bottom item takes the higest precedence, and the top item takes the lowest precedence. + # The bottom item takes the highest precedence, and the top item takes the lowest precedence. enum data_store: { redis: 1, database: 2, diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index f0ae516a2f8..d4586219333 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -12,6 +12,10 @@ module Ci include AtomicInternalId include EnumWithNil include HasRef + include ShaAttribute + + sha_attribute :source_sha + sha_attribute :target_sha belongs_to :project, inverse_of: :all_pipelines belongs_to :user @@ -47,6 +51,8 @@ module Ci has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id' has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id' + has_one :chat_data, class_name: 'Ci::PipelineChatData' + accepts_nested_attributes_for :variables, reject_if: :persisted? delegate :id, to: :project, prefix: true @@ -189,6 +195,22 @@ module Ci .sort_by_merge_request_pipelines end + scope :triggered_by_merge_request, -> (merge_request) do + where(source: :merge_request, merge_request: merge_request) + end + + scope :detached_merge_request_pipelines, -> (merge_request) do + triggered_by_merge_request(merge_request).where(target_sha: nil) + end + + scope :merge_request_pipelines, -> (merge_request) do + triggered_by_merge_request(merge_request).where.not(target_sha: nil) + end + + scope :mergeable_merge_request_pipelines, -> (merge_request) do + triggered_by_merge_request(merge_request).where(target_sha: merge_request.target_branch_sha) + end + # Returns the pipelines in descending order (= newest first), optionally # limited to a number of references. # @@ -622,6 +644,8 @@ module Ci variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s) if merge_request? && merge_request + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s) + variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s) variables.concat(merge_request.predefined_variables) end end @@ -650,9 +674,9 @@ module Ci def all_merge_requests @all_merge_requests ||= if merge_request? - project.merge_requests.where(id: merge_request_id) + MergeRequest.where(id: merge_request_id) else - project.merge_requests.where(source_branch: ref) + MergeRequest.where(source_project_id: project_id, source_branch: ref) end end @@ -706,6 +730,22 @@ module Ci ref == project.default_branch end + def triggered_by_merge_request? + merge_request? && merge_request_id.present? + end + + def detached_merge_request_pipeline? + triggered_by_merge_request? && target_sha.nil? + end + + def merge_request_pipeline? + triggered_by_merge_request? && target_sha.present? + end + + def mergeable_merge_request_pipeline? + triggered_by_merge_request? && target_sha == merge_request.target_branch_sha + end + private def ci_yaml_from_repo diff --git a/app/models/ci/pipeline_chat_data.rb b/app/models/ci/pipeline_chat_data.rb new file mode 100644 index 00000000000..8d37500fec5 --- /dev/null +++ b/app/models/ci/pipeline_chat_data.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Ci + class PipelineChatData < ActiveRecord::Base + self.table_name = 'ci_pipeline_chat_data' + + belongs_to :chat_name + + validates :pipeline_id, presence: true + validates :chat_name_id, presence: true + validates :response_url, presence: true + end +end diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index 2994aaae4aa..4be4fdb1ff2 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -22,6 +22,7 @@ module Ci schedule: 4, api: 5, external: 6, + chat: 8, merge_request: 10 } end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 5aae31de6e2..d82e11bbb89 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -97,6 +97,7 @@ module Ci scope :order_contacted_at_asc, -> { order(contacted_at: :asc) } scope :order_created_at_desc, -> { order(created_at: :desc) } + scope :with_tags, -> { preload(:tags) } validate :tag_constraints validates :access_level, presence: true diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 52c440ffb2f..fa7ce363531 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -6,7 +6,6 @@ module Clusters include PrometheusAdapter VERSION = '6.7.3' - READY_STATUS = [:installed, :updating, :updated, :update_errored].freeze self.table_name = 'clusters_applications_prometheus' @@ -25,10 +24,6 @@ module Clusters end end - def ready? - READY_STATUS.include?(status_name) - end - def chart 'stable/prometheus' end diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index f17da0bb7b1..941551dadaa 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 < ActiveRecord::Base - VERSION = '0.1.45'.freeze + VERSION = '0.2.0'.freeze self.table_name = 'clusters_applications_runners' diff --git a/app/models/concerns/closed_at_filterable.rb b/app/models/concerns/closed_at_filterable.rb new file mode 100644 index 00000000000..239c2e47611 --- /dev/null +++ b/app/models/concerns/closed_at_filterable.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ClosedAtFilterable + extend ActiveSupport::Concern + + included do + scope :closed_before, ->(date) { where(scoped_table[:closed_at].lteq(date)) } + scope :closed_after, ->(date) { where(scoped_table[:closed_at].gteq(date)) } + + def self.scoped_table + arel_table.alias(table_name) + end + end +end diff --git a/app/models/concerns/fast_destroy_all.rb b/app/models/concerns/fast_destroy_all.rb index 1e3afd641ed..f862031bce0 100644 --- a/app/models/concerns/fast_destroy_all.rb +++ b/app/models/concerns/fast_destroy_all.rb @@ -11,7 +11,7 @@ # it is difficult to accomplish it. # # This module defines a format to use `delete_all` and delete associated external data. -# Here is an exmaple +# Here is an example # # Situation # - `Project` has many `Ci::BuildTraceChunk` through `Ci::Build` diff --git a/app/models/concerns/iid_routes.rb b/app/models/concerns/iid_routes.rb index b7f99e845ca..3eeb29b6595 100644 --- a/app/models/concerns/iid_routes.rb +++ b/app/models/concerns/iid_routes.rb @@ -4,7 +4,7 @@ module IidRoutes ## # This automagically enforces all related routes to use `iid` instead of `id` # If you want to use `iid` for some routes and `id` for other routes, this module should not to be included, - # instead you should define `iid` or `id` explictly at each route generators. e.g. pipeline_path(project.id, pipeline.iid) + # instead you should define `iid` or `id` explicitly at each route generators. e.g. pipeline_path(project.id, pipeline.iid) def to_param iid.to_s end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 0a77fbeba08..670103bc3f3 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -23,11 +23,12 @@ module Issuable include Sortable include CreatedAtFilterable include UpdatedAtFilterable + include ClosedAtFilterable # This object is used to gather issuable meta data for displaying # upvotes, downvotes, notes and closing merge requests count for issues and merge requests # lists avoiding n+1 queries and improving performance. - IssuableMeta = Struct.new(:upvotes, :downvotes, :notes_count, :merge_requests_count) + IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :merge_requests_count) included do cache_markdown_field :title, pipeline: :single_line @@ -35,8 +36,8 @@ module Issuable redact_field :description - belongs_to :author, class_name: "User" - belongs_to :updated_by, class_name: "User" + belongs_to :author, class_name: 'User' + belongs_to :updated_by, class_name: 'User' belongs_to :last_edited_by, class_name: 'User' belongs_to :milestone diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb index e51b4e22c96..a479bef993c 100644 --- a/app/models/concerns/sha_attribute.rb +++ b/app/models/concerns/sha_attribute.rb @@ -16,6 +16,8 @@ module ShaAttribute # the column is the correct type. In production it should behave like any other attribute. # See https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5502 for more discussion def validate_binary_column_exists!(name) + return unless database_exists? + unless table_exists? warn "WARNING: sha_attribute #{name.inspect} is invalid since the table doesn't exist - you may need to run database migrations" return @@ -35,5 +37,13 @@ module ShaAttribute Gitlab::AppLogger.error "ShaAttribute initialization: #{error.message}" raise end + + def database_exists? + ActiveRecord::Base.connection + + true + rescue + false + end end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 1fc088b12ae..87bdb52b58b 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -243,6 +243,10 @@ class Environment < ActiveRecord::Base self.environment_type || self.name end + def name_without_type + @name_without_type ||= name.delete_prefix("#{environment_type}/") + end + def deployment_platform strong_memoize(:deployment_platform) do project.deployment_platform(environment: self.name) diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb index 31084c54bdc..57283a78ea9 100644 --- a/app/models/error_tracking/project_error_tracking_setting.rb +++ b/app/models/error_tracking/project_error_tracking_setting.rb @@ -58,7 +58,7 @@ module ErrorTracking def list_sentry_issues(opts = {}) with_reactive_cache('list_issues', opts.stringify_keys) do |result| - { issues: result } + result end end @@ -69,8 +69,10 @@ module ErrorTracking def calculate_reactive_cache(request, opts) case request when 'list_issues' - sentry_client.list_issues(**opts.symbolize_keys) + { issues: sentry_client.list_issues(**opts.symbolize_keys) } end + rescue Sentry::Client::Error => e + { error: e.message } end # http://HOST/api/0/projects/ORG/PROJECT diff --git a/app/models/issue.rb b/app/models/issue.rb index 182c5d3d4b0..071ad50fddc 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -66,6 +66,7 @@ class Issue < ActiveRecord::Base scope :preload_associations, -> { preload(:labels, project: :namespace) } scope :public_only, -> { where(confidential: false) } + scope :confidential_only, -> { where(confidential: true) } after_save :expire_etag_cache after_save :ensure_metrics, unless: :imported? @@ -262,6 +263,10 @@ class Issue < ActiveRecord::Base end # rubocop: enable CodeReuse/ServiceClass + def merge_requests_count + merge_requests_closing_issues.count + end + private def ensure_metrics diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 75fca96ce0a..1468ae1c34a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -764,6 +764,16 @@ class MergeRequest < ActiveRecord::Base true end + def mergeable_to_ref? + return false if merged? + return false if broken? + + # 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 @@ -1077,6 +1087,10 @@ class MergeRequest < ActiveRecord::Base "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/head" end + def merge_ref_path + "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/merge" + end + def in_locked_state begin lock_mr diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index 6da3bb7bfb7..ecbeb24ee0a 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -40,9 +40,12 @@ module Network # Get commits from repository # def collect_commits - find_commits(count_to_display_commit_in_center).map do |commit| - # Decorate with app/model/network/commit.rb - Network::Commit.new(commit) + # https://gitlab.com/gitlab-org/gitlab-ce/issues/58013 + Gitlab::GitalyClient.allow_n_plus_1_calls do + find_commits(count_to_display_commit_in_center).map do |commit| + # Decorate with app/model/network/commit.rb + Network::Commit.new(commit) + end end end diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb index 9f16eefe074..481c1d963c6 100644 --- a/app/models/notification_recipient.rb +++ b/app/models/notification_recipient.rb @@ -153,7 +153,7 @@ class NotificationRecipient user.global_notification_setting end - # Returns the notificaton_setting of the lowest group in hierarchy with non global level + # Returns the notification_setting of the lowest group in hierarchy with non global level def closest_non_global_group_notification_settting return unless @group return if indexed_group_notification_settings.empty? diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index 73a58f2420e..ed78a46eaf3 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -2,8 +2,11 @@ class PersonalAccessToken < ActiveRecord::Base include Expirable + include IgnorableColumn include TokenAuthenticatable - add_authentication_token_field :token, digest: true, fallback: true + + add_authentication_token_field :token, digest: true + ignore_column :token REDIS_EXPIRY_TIME = 3.minutes diff --git a/app/models/project.rb b/app/models/project.rb index c72d3a3b725..b016a65f0bb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -160,6 +160,7 @@ class Project < ActiveRecord::Base has_one :pushover_service has_one :jira_service has_one :redmine_service + has_one :youtrack_service has_one :custom_issue_tracker_service has_one :bugzilla_service has_one :gitlab_issue_tracker_service, inverse_of: :project @@ -248,10 +249,10 @@ class Project < ActiveRecord::Base has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :commit_statuses - # The relation :all_pipelines is intented to be used when we want to get the + # The relation :all_pipelines is intended to be used when we want to get the # whole list of pipelines associated to the project has_many :all_pipelines, class_name: 'Ci::Pipeline', inverse_of: :project - # The relation :ci_pipelines is intented to be used when we want to get only + # The relation :ci_pipelines is intended to be used when we want to get only # those pipeline which are directly related to CI. There are # other pipelines, like webide ones, that we won't retrieve # if we use this relation. @@ -305,6 +306,7 @@ class Project < ActiveRecord::Base delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings delegate :group_clusters_enabled?, to: :group, allow_nil: true delegate :root_ancestor, to: :namespace, allow_nil: true + delegate :last_pipeline, to: :commit, allow_nil: true # Validations validates :creator, presence: true, on: :create @@ -595,6 +597,14 @@ class Project < ActiveRecord::Base end end + def ci_pipelines + if builds_enabled? + super + else + super.external + end + end + # returns all ancestor-groups upto but excluding the given namespace # when no namespace is given, all ancestors upto the top are returned def ancestors_upto(top = nil, hierarchy_order: nil) @@ -1206,7 +1216,7 @@ class Project < ActiveRecord::Base "#{web_url}.git" end - # Is overriden in EE + # Is overridden in EE def lfs_http_url_to_repo(_) http_url_to_repo end @@ -1829,7 +1839,7 @@ class Project < ActiveRecord::Base # Set repository as writable again def set_repository_writable! with_lock do - update_column(repository_read_only, false) + update_column(:repository_read_only, false) end end @@ -1916,6 +1926,14 @@ class Project < ActiveRecord::Base persisted? && path_changed? end + def human_merge_method + if merge_method == :ff + 'Fast-forward' + else + merge_method.to_s.humanize + end + end + def merge_method if self.merge_requests_ff_only_enabled :ff diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 6c82e088231..6a454070fe2 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -22,6 +22,10 @@ class SlackSlashCommandsService < SlashCommandsService end end + def chat_responder + ::Gitlab::Chat::Responder::Slack + end + private def format(text) diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb new file mode 100644 index 00000000000..957be685aea --- /dev/null +++ b/app/models/project_services/youtrack_service.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +class YoutrackService < IssueTrackerService + validates :project_url, :issues_url, presence: true, public_url: true, if: :activated? + + prop_accessor :description, :project_url, :issues_url + + # {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1 + def self.reference_pattern(only_long: false) + if only_long + /(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)/ + else + /(?<issue>\b[A-Z][A-Za-z0-9_]*-\d+)|(#{Issue.reference_prefix}(?<issue>\d+))/ + end + end + + def title + 'YouTrack' + end + + def description + if self.properties && self.properties['description'].present? + self.properties['description'] + else + 'YouTrack issue tracker' + end + end + + def self.to_param + 'youtrack' + end + + def fields + [ + { type: 'text', name: 'description', placeholder: description }, + { type: 'text', name: 'project_url', placeholder: 'Project url', required: true }, + { type: 'text', name: 'issues_url', placeholder: 'Issue url', required: true } + ] + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb index ed55a6e572b..cd761a29618 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -854,6 +854,12 @@ class Repository end end + def merge_to_ref(user, source_sha, merge_request, target_ref, message) + branch = merge_request.target_branch + + raw.merge_to_ref(user, source_sha, branch, target_ref, message) + end + def ff_merge(user, source, target_branch, merge_request: nil) their_commit_id = commit(source)&.id raise 'Invalid merge source' if their_commit_id.nil? diff --git a/app/models/service.rb b/app/models/service.rb index 3461e0bfe70..da523bfa426 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -266,6 +266,7 @@ class Service < ActiveRecord::Base prometheus pushover redmine + youtrack slack_slash_commands slack teamcity diff --git a/app/models/user.rb b/app/models/user.rb index fd32d838e53..ee51c35d576 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -388,7 +388,7 @@ class User < ApplicationRecord find_by(id: user_id) end - def filter(filter_name) + def filter_items(filter_name) case filter_name when 'admins' admins diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index cadbc5ae009..95dd8b2795e 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -152,7 +152,6 @@ class ProjectPolicy < BasePolicy enable :remove_fork_project enable :destroy_merge_request enable :destroy_issue - enable :remove_pages enable :set_issue_iid enable :set_issue_created_at @@ -271,6 +270,7 @@ class ProjectPolicy < BasePolicy enable :admin_pages enable :read_pages enable :update_pages + enable :remove_pages enable :read_cluster enable :add_cluster enable :create_cluster diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb index 300f85e1e9d..d60281c8a0b 100644 --- a/app/presenters/ci/build_runner_presenter.rb +++ b/app/presenters/ci/build_runner_presenter.rb @@ -2,6 +2,11 @@ module Ci class BuildRunnerPresenter < SimpleDelegator + include Gitlab::Utils::StrongMemoize + + RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze + RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze + def artifacts return unless options[:artifacts] @@ -11,6 +16,35 @@ module Ci list.flatten.compact end + def ref_type + if tag + 'tag' + else + 'branch' + end + end + + def git_depth + strong_memoize(:git_depth) do + git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value) + git_depth.to_i + end + end + + def refspecs + specs = [] + + if git_depth > 0 + specs << refspec_for_branch(ref) if branch? || merge_request? + specs << refspec_for_tag(ref) if tag? + else + specs << refspec_for_branch + specs << refspec_for_tag + end + + specs + end + private def create_archive(artifacts) @@ -41,5 +75,13 @@ module Ci } end end + + def refspec_for_branch(ref = '*') + "+#{Gitlab::Git::BRANCH_REF_PREFIX}#{ref}:#{RUNNER_REMOTE_BRANCH_PREFIX}#{ref}" + end + + def refspec_for_tag(ref = '*') + "+#{Gitlab::Git::TAG_REF_PREFIX}#{ref}:#{RUNNER_REMOTE_TAG_PREFIX}#{ref}" + end end end diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb index 4a7d13915dd..76248e6470e 100644 --- a/app/serializers/environment_entity.rb +++ b/app/serializers/environment_entity.rb @@ -8,6 +8,7 @@ class EnvironmentEntity < Grape::Entity expose :state expose :external_url expose :environment_type + expose :name_without_type expose :last_deployment, using: DeploymentEntity expose :stop_action_available?, as: :has_stop_action diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 354e53a367c..8973c5ffc9e 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -25,7 +25,9 @@ module Ci origin_ref: params[:ref], checkout_sha: params[:checkout_sha], after_sha: params[:after], - before_sha: params[:before], + before_sha: params[:before], # The base SHA of the source branch (i.e merge_request.diff_base_sha). + source_sha: params[:source_sha], # The HEAD SHA of the source branch (i.e merge_request.diff_head_sha). + target_sha: params[:target_sha], # The HEAD SHA of the target branch. trigger_request: trigger_request, schedule: schedule, merge_request: merge_request, @@ -36,6 +38,7 @@ module Ci project: project, current_user: current_user, push_options: params[:push_options], + chat_data: params[:chat_data], **extra_options(options)) sequence = Gitlab::Ci::Pipeline::Chain::Sequence @@ -111,10 +114,10 @@ module Ci def extra_options(options = {}) # In Ruby 2.4, even when options is empty, f(**options) doesn't work when f # doesn't have any parameters. We reproduce the Ruby 2.5 behavior by - # checking explicitely that no arguments are given. + # checking explicitly that no arguments are given. raise ArgumentError if options.any? - {} # overriden in EE + {} # overridden in EE end end end diff --git a/app/services/ci/pipeline_trigger_service.rb b/app/services/ci/pipeline_trigger_service.rb index 4ba3f5fb8ba..2dbb7c3917d 100644 --- a/app/services/ci/pipeline_trigger_service.rb +++ b/app/services/ci/pipeline_trigger_service.rb @@ -38,11 +38,11 @@ module Ci end def create_pipeline_from_job(job) - # overriden in EE + # overridden in EE end def job_from_token - # overriden in EE + # overridden in EE end def variables diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb index 92c2c1b9834..12f8c849d41 100644 --- a/app/services/clusters/applications/create_service.rb +++ b/app/services/clusters/applications/create_service.rb @@ -27,9 +27,11 @@ module Clusters application.oauth_application = create_oauth_application(application, request) end - application.save! + worker = application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker - Clusters::Applications::ScheduleInstallationService.new(application).execute + application.make_scheduled! + + worker.perform_async(application.name, application.id) end end diff --git a/app/services/clusters/applications/schedule_installation_service.rb b/app/services/clusters/applications/schedule_installation_service.rb deleted file mode 100644 index 15c93f1e79b..00000000000 --- a/app/services/clusters/applications/schedule_installation_service.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Applications - class ScheduleInstallationService - attr_reader :application - - def initialize(application) - @application = application - end - - def execute - application.updateable? ? schedule_upgrade : schedule_install - end - - private - - def schedule_upgrade - application.make_scheduled! - - ClusterUpgradeAppWorker.perform_async(application.name, application.id) - end - - def schedule_install - application.make_scheduled! - - ClusterInstallAppWorker.perform_async(application.name, application.id) - end - end - end -end diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb index 5b408bd96c7..6713b6617ae 100644 --- a/app/services/concerns/users/participable_service.rb +++ b/app/services/concerns/users/participable_service.rb @@ -11,7 +11,7 @@ module Users def noteable_owner return [] unless noteable && noteable.author.present? - [as_hash(noteable.author)] + [user_as_hash(noteable.author)] end def participants_in_noteable @@ -23,21 +23,24 @@ module Users def sorted(users) users.uniq.to_a.compact.sort_by(&:username).map do |user| - as_hash(user) + user_as_hash(user) end end def groups current_user.authorized_groups.sort_by(&:path).map do |group| - count = group.users.count - { username: group.full_path, name: group.full_name, count: count, avatar_url: group.avatar_url } + group_as_hash(group) end end private - def as_hash(user) - { username: user.username, name: user.name, avatar_url: user.avatar_url } + def user_as_hash(user) + { type: user.class.name, username: user.username, name: user.name, avatar_url: user.avatar_url } + end + + def group_as_hash(group) + { type: group.class.name, username: group.full_path, name: group.full_name, avatar_url: group.avatar_url, count: group.users.count } end end end diff --git a/app/services/error_tracking/list_issues_service.rb b/app/services/error_tracking/list_issues_service.rb index 4cc35cfa4a8..a6c6bec9598 100644 --- a/app/services/error_tracking/list_issues_service.rb +++ b/app/services/error_tracking/list_issues_service.rb @@ -6,15 +6,19 @@ module ErrorTracking DEFAULT_LIMIT = 20 def execute - return error('not enabled') unless enabled? - return error('access denied') unless can_read? + return error('Error Tracking is not enabled') unless enabled? + return error('Access denied', :unauthorized) unless can_read? result = project_error_tracking_setting .list_sentry_issues(issue_status: issue_status, limit: limit) # our results are not yet ready unless result - return error('not ready', :no_content) + return error('Not ready. Try again later', :no_content) + end + + if result[:error].present? + return error(result[:error], :bad_request) end success(issues: result[:issues]) diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 092fd64574d..f387c749a21 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -235,6 +235,6 @@ class GitPushService < BaseService private def pipeline_options - {} # to be overriden in EE + {} # to be overridden in EE end end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 6fef5b3ed1d..e39b3603c6c 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -61,6 +61,6 @@ class GitTagPushService < BaseService end def pipeline_options - {} # to be overriden in EE + {} # to be overridden in EE end end diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index 55a3b9fa7b1..99ead467f74 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -33,7 +33,7 @@ module Groups private def after_build_hook(group, params) - # overriden in EE + # overridden in EE end def create_chat_team? diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index 9ff1da270e2..787445180f0 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -31,7 +31,7 @@ module Groups private def before_assignment_hook(group, params) - # overriden in EE + # overridden in EE end def after_update diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb new file mode 100644 index 00000000000..095bdca5472 --- /dev/null +++ b/app/services/merge_requests/merge_base_service.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module MergeRequests + class MergeBaseService < MergeRequests::BaseService + include Gitlab::Utils::StrongMemoize + + MergeError = Class.new(StandardError) + + attr_reader :merge_request + + # Overridden in EE. + def hooks_validation_pass?(_merge_request) + true + end + + # Overridden in EE. + def hooks_validation_error(_merge_request) + # No-op + end + + def source + if merge_request.squash + squash_sha! + else + merge_request.diff_head_sha + end + end + + private + + # Overridden in EE. + def error_check! + # No-op + end + + def raise_error(message) + raise MergeError, message + end + + def handle_merge_error(*args) + # No-op + end + + def commit_message + params[:commit_message] || + merge_request.default_merge_commit_message + end + + def squash_sha! + strong_memoize(:squash_sha) do + params[:merge_request] = merge_request + squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute + + case squash_result[:status] + when :success + squash_result[:squash_sha] + when :error + raise ::MergeRequests::MergeService::MergeError, squash_result[:message] + end + end + end + end +end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 449997bcf07..8241e408ce5 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -7,13 +7,7 @@ module MergeRequests # mark merge request as merged and execute all hooks and notifications # Executed when you do merge via GitLab UI # - class MergeService < MergeRequests::BaseService - include Gitlab::Utils::StrongMemoize - - MergeError = Class.new(StandardError) - - attr_reader :merge_request, :source - + class MergeService < MergeRequests::MergeBaseService delegate :merge_jid, :state, to: :@merge_request def execute(merge_request) @@ -24,7 +18,7 @@ module MergeRequests @merge_request = merge_request - error_check! + validate! merge_request.in_locked_state do if commit @@ -38,22 +32,22 @@ module MergeRequests handle_merge_error(log_message: e.message, save_message_on_model: true) end - def source - if merge_request.squash - squash_sha! - else - merge_request.diff_head_sha - end - end + private - # Overridden in EE. - def hooks_validation_pass?(_merge_request) - true + def validate! + authorization_check! + error_check! end - private + def authorization_check! + unless @merge_request.can_be_merged_by?(current_user) + raise_error('You are not allowed to merge this merge request') + end + end def error_check! + super + error = if @merge_request.should_be_rebased? 'Only fast-forward merge is allowed for your project. Please update your source branch' @@ -63,7 +57,7 @@ module MergeRequests 'No source for merge' end - raise MergeError, error if error + raise_error(error) if error end def commit @@ -73,36 +67,20 @@ module MergeRequests if commit_id log_info("Git merge finished on JID #{merge_jid} commit #{commit_id}") else - raise MergeError, 'Conflicts detected during merge' + raise_error('Conflicts detected during merge') end merge_request.update!(merge_commit_sha: commit_id) end - def squash_sha! - strong_memoize(:squash_sha) do - params[:merge_request] = merge_request - squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute - - case squash_result[:status] - when :success - squash_result[:squash_sha] - when :error - raise ::MergeRequests::MergeService::MergeError, squash_result[:message] - end - end - end - def try_merge - message = params[:commit_message] || merge_request.default_merge_commit_message - - repository.merge(current_user, source, merge_request, message) + repository.merge(current_user, source, merge_request, commit_message) rescue Gitlab::Git::PreReceiveError => e handle_merge_error(log_message: e.message) - raise MergeError, 'Something went wrong during merge pre-receive hook' + raise_error('Something went wrong during merge pre-receive hook') rescue => e handle_merge_error(log_message: e.message) - raise MergeError, 'Something went wrong during merge' + raise_error('Something went wrong during merge') ensure merge_request.update!(in_progress_merge_commit_sha: nil) end diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb new file mode 100644 index 00000000000..586652ae44e --- /dev/null +++ b/app/services/merge_requests/merge_to_ref_service.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module MergeRequests + # Performs the merge between source SHA and the target branch. Instead + # of writing the result to the MR target branch, it targets the `target_ref`. + # + # Ideally this should leave the `target_ref` state with the same state the + # target branch would have if we used the regular `MergeService`, but without + # every side-effect that comes with it (MR updates, mails, source branch + # deletion, etc). This service should be kept idempotent (i.e. can + # be executed regardless of the `target_ref` current state). + # + class MergeToRefService < MergeRequests::MergeBaseService + def execute(merge_request) + @merge_request = merge_request + + validate! + + commit_id = commit + + raise_error('Conflicts detected during merge') unless commit_id + + success(commit_id: commit_id) + rescue MergeError => error + error(error.message) + end + + private + + def validate! + authorization_check! + error_check! + end + + def error_check! + super + + error = + if Feature.disabled?(:merge_to_tmp_merge_ref_path, project) + 'Feature is not enabled' + elsif !merge_method_supported? + "#{project.human_merge_method} to #{target_ref} is currently not supported." + elsif !hooks_validation_pass?(merge_request) + hooks_validation_error(merge_request) + elsif @merge_request.should_be_rebased? + 'Fast-forward merge is not possible. Please update your source branch.' + elsif !@merge_request.mergeable_to_ref? + "Merge request is not mergeable to #{target_ref}" + elsif !source + '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 + + def commit + repository.merge_to_ref(current_user, source, merge_request, target_ref, commit_message) + rescue Gitlab::Git::PreReceiveError => error + raise MergeError, error.message + end + + def merge_method_supported? + [:merge, :rebase_merge].include?(project.merge_method) + end + end +end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 91091c4393d..fc234bafc57 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -38,8 +38,8 @@ module Projects new_params = { visibility_level: allowed_visibility_level, description: @project.description, - name: @project.name, - path: @project.path, + name: target_name, + path: target_path, shared_runners_enabled: @project.shared_runners_enabled, namespace_id: target_namespace.id, fork_network: fork_network, @@ -94,6 +94,14 @@ module Projects Projects::ForksCountService.new(@project).refresh_cache end + def target_path + @target_path ||= @params[:path] || @project.path + end + + def target_name + @target_name ||= @params[:name] || @project.name + end + def target_namespace @target_namespace ||= @params[:namespace] || current_user.namespace end diff --git a/app/services/prometheus/adapter_service.rb b/app/services/prometheus/adapter_service.rb index a791845ba20..3be958e1613 100644 --- a/app/services/prometheus/adapter_service.rb +++ b/app/services/prometheus/adapter_service.rb @@ -30,7 +30,7 @@ module Prometheus return unless deployment_platform.respond_to?(:cluster) cluster = deployment_platform.cluster - return unless cluster.application_prometheus&.ready? + return unless cluster.application_prometheus&.available? cluster.application_prometheus end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index ec6c306227b..ea8ac7e4656 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -360,7 +360,7 @@ module SystemNoteService # author - User performing the change # branch_type - 'source' or 'target' # old_branch - old branch name - # new_branch - new branch nmae + # new_branch - new branch name # # Example Note text: # diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb index d3e32818dc7..3fd015c3cf5 100644 --- a/app/validators/url_validator.rb +++ b/app/validators/url_validator.rb @@ -93,6 +93,12 @@ class UrlValidator < ActiveModel::EachValidator end def allow_setting_local_requests? + # We cannot use Gitlab::CurrentSettings as ApplicationSetting itself + # uses UrlValidator to validate urls. This ends up in a cycle + # when Gitlab::CurrentSettings creates an ApplicationSetting which then + # calls this validator. + # + # See https://gitlab.com/gitlab-org/gitlab-ee/issues/9833 ApplicationSetting.current&.allow_local_requests_from_hooks_and_services? end end diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 77e84abd76e..a5f34d0dab2 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -42,6 +42,7 @@ %br Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior. + = render partial: 'admin/appearances/system_header_footer_form', locals: { form: f } %hr .row diff --git a/app/views/admin/appearances/_system_header_footer_form.html.haml b/app/views/admin/appearances/_system_header_footer_form.html.haml new file mode 100644 index 00000000000..ca9d6adebeb --- /dev/null +++ b/app/views/admin/appearances/_system_header_footer_form.html.haml @@ -0,0 +1,24 @@ +- form = local_assigns.fetch(:form) + +%hr +.row + .col-lg-4.profile-settings-sidebar + %h4.prepend-top-0 + = _('System header and footer') + + .col-lg-8 + .form-group + = form.label :header_message, _('Header message'), class: 'col-form-label label-bold' + = form.text_area :header_message, placeholder: _('State your message to activate'), class: "form-control js-autosize" + .form-group + = form.label :footer_message, _('Footer message'), class: 'col-form-label label-bold' + = form.text_area :footer_message, placeholder: _('State your message to activate'), class: "form-control js-autosize" + .form-group.js-toggle-colors-container + %button.btn.btn-link.js-toggle-colors-link{ type: 'button' } + = _('Customize colors') + .form-group.js-toggle-colors-container.hide + = form.label :message_background_color, _('Background Color'), class: 'col-form-label label-bold' + = form.color_field :message_background_color, class: "form-control" + .form-group.js-toggle-colors-container.hide + = form.label :message_font_color, _('Font Color'), class: 'col-form-label label-bold' + = form.color_field :message_font_color, class: "form-control" diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 0a688b90f3a..395c469255e 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -22,7 +22,7 @@ %span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) } = visibility_level_icon(group.visibility_level, fw: false) - .avatar-container.s40 + .avatar-container.rect-avatar.s40 = group_icon(group, class: "avatar s40 d-none d-sm-block") .title = link_to [:admin, group], class: 'group-name' do diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 93da87538bc..00d255846f9 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -15,7 +15,7 @@ = _('Group info:') %ul.content-list %li - .avatar-container.s60 + .avatar-container.rect-avatar.s60 = group_icon(@group, class: "avatar s60") %li %span.light= _('Name:') diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml index 50296a2afe7..5bc695aa7b5 100644 --- a/app/views/admin/projects/_projects.html.haml +++ b/app/views/admin/projects/_projects.html.haml @@ -19,7 +19,7 @@ .title = link_to(admin_namespace_project_path(project.namespace, project)) do .dash-project-avatar - .avatar-container.s40 + .avatar-container.rect-avatar.s40 = project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40) %span.project-full-name %span.namespace-name diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index ec57eb1ed08..ecf2b1d60ba 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -44,12 +44,12 @@ .table-section.section-5 .table-mobile-header{ role: 'rowheader' }= _('Jobs') .table-mobile-content - = runner.builds.count(:all) + = limited_counter_with_delimiter(runner.builds) .table-section.section-10.section-wrap .table-mobile-header{ role: 'rowheader' }= _('Tags') .table-mobile-content - - runner.tag_list.sort.each do |tag| + - runner.tags.map(&:name).sort.each do |tag| %span.badge.badge-primary = tag diff --git a/app/views/clusters/clusters/_form.html.haml b/app/views/clusters/clusters/_form.html.haml index 7acd9ce0562..9fb91a39387 100644 --- a/app/views/clusters/clusters/_form.html.haml +++ b/app/views/clusters/clusters/_form.html.haml @@ -28,7 +28,7 @@ .form-group %h5= s_('ClusterIntegration|Base domain') - = field.text_field :base_domain, class: 'col-md-6 form-control js-select-on-focus' + = field.text_field :base_domain, class: 'col-md-6 form-control js-select-on-focus qa-base-domain' .form-text.text-muted - auto_devops_url = help_page_path('topics/autodevops/index') - auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url } @@ -43,4 +43,4 @@ - if can?(current_user, :update_cluster, @cluster) .form-group - = field.submit _('Save changes'), class: 'btn btn-success' + = field.submit _('Save changes'), class: 'btn btn-success qa-save-domain' diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 8d99f84755a..a05d0190efb 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,9 +1,9 @@ .page-title-holder %h1.page-title= _('Snippets') - - if current_user + - if current_user && current_user.snippets.any? || @snippets.any? .page-title-controls - = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet" + = link_to _("New snippet"), new_snippet_path, class: "btn btn-success", title: _("New snippet") .top-area %ul.nav-links.nav.nav-tabs diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 6eb067da95c..b649fe91c24 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -3,6 +3,14 @@ - header_title "Snippets", dashboard_snippets_path = render 'dashboard/snippets_head' -= render partial: 'snippets/snippets_scope_menu', locals: { include_private: true } +- if current_user.snippets.exists? + = render partial: 'snippets/snippets_scope_menu', locals: { include_private: true } -= render partial: 'snippets/snippets', locals: { link_project: true } +.d-block.d-sm-none + + = link_to _("New snippet"), new_snippet_path, class: "btn btn-success btn-block", title: _("New snippet") + +- if current_user.snippets.exists? + = render partial: 'shared/snippets/list', locals: { link_project: true } +- else + = render 'shared/empty_states/snippets', button_path: new_snippet_path diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 94fc4ac21d2..d23c8301b10 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -7,4 +7,4 @@ - else = render 'explore/head' -= render partial: 'snippets/snippets', locals: { link_project: true } += render partial: 'shared/snippets/list', locals: { link_project: true } diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index 3a8d95f44d1..39c0c113793 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -3,7 +3,7 @@ .group-home-panel .row.mb-3 .home-panel-title-row.col-md-12.col-lg-6.d-flex - .avatar-container.home-panel-avatar.append-right-default.float-none + .avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none = group_icon(@group, class: 'avatar avatar-tile s64', width: 64, height: 64) .d-flex.flex-column.flex-wrap.align-items-baseline .d-inline-flex.align-items-baseline diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml index 0424ece037d..9ed71d19d32 100644 --- a/app/views/groups/settings/_general.html.haml +++ b/app/views/groups/settings/_general.html.haml @@ -20,7 +20,7 @@ = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group .form-group.prepend-top-default.append-bottom-20 - .avatar-container.s90 + .avatar-container.rect-avatar.s90 = group_icon(@group, alt: '', class: 'avatar group-avatar s90') = f.label :avatar, _('Group avatar'), class: 'label-bold d-block' = render 'shared/choose_group_avatar_button', f: f diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 28ffb2dd63c..efb3815b257 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -356,6 +356,18 @@ %td.shortcut %kbd l %td Change Label + %tr + %td.shortcut + %kbd ] + \/ + %kbd j + %td Move to next file + %tr + %td.shortcut + %kbd [ + \/ + %kbd k + %td Move to previous file %tbody.hidden-shortcut{ style: 'display:none' } %tr %th diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 0bb2363f65a..11e83ddfe64 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -38,6 +38,8 @@ = stylesheet_link_tag 'performance_bar' if performance_bar_enabled? = stylesheet_link_tag 'csslab' if Feature.enabled?(:csslab) + = stylesheet_link_tag "highlight/themes/#{user_color_scheme}", media: "all" + = Gon::Base.render_data - if content_for?(:library_javascripts) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 4373240001e..043cca6ad38 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -5,7 +5,9 @@ = render "layouts/init_auto_complete" if @gfm_form = render "layouts/init_client_detection_flags" = render 'peek/bar' - = render partial: "layouts/header/default", locals: { project: @project, group: @group } + = header_message + = render partial: "layouts/header/default", locals: { project: @project, group: @group } = render 'layouts/page', sidebar: sidebar, nav: nav + = footer_message = yield :scripts_body diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 6003d973c88..2f3c13aaf6e 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -2,6 +2,7 @@ %html.devise-layout-html{ class: system_message_class } = render "layouts/head" %body.ui-indigo.login-page.application.navless.qa-login-page{ data: { page: body_data_page } } + = header_message .page-wrap = render "layouts/header/empty" .login-page-broadcast @@ -34,3 +35,4 @@ = link_to _("Explore"), explore_root_path = link_to _("Help"), help_path = link_to _("About GitLab"), "https://about.gitlab.com/" + = footer_message diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml index 663e5b24368..6c9c8aa4431 100644 --- a/app/views/layouts/devise_empty.html.haml +++ b/app/views/layouts/devise_empty.html.haml @@ -2,6 +2,7 @@ %html{ lang: "en", class: system_message_class } = render "layouts/head" %body.ui-indigo.login-page.application.navless + = header_message = render "layouts/header/empty" = render "layouts/broadcast" .container.navless-container @@ -15,3 +16,4 @@ = link_to _("Explore"), explore_root_path = link_to _("Help"), help_path = link_to _("About GitLab"), "https://about.gitlab.com/" + = footer_message diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 3fbaaafe89e..21ea9f3b2f3 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -6,7 +6,7 @@ .nav-sidebar-inner-scroll .context-header = link_to group_path(@group), title: @group.name do - .avatar-container.s40.group-avatar + .avatar-container.rect-avatar.s40.group-avatar = group_icon(@group, class: "avatar s40 avatar-tile") .sidebar-context-title = @group.name diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index dd7833647b7..7b492efeb09 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -3,7 +3,7 @@ - can_edit = can?(current_user, :admin_project, @project) .context-header = link_to project_path(@project), title: @project.name do - .avatar-container.s40.project-avatar + .avatar-container.rect-avatar.s40.project-avatar = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile', width: 40, height: 40) .sidebar-context-title = @project.name diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 1a9aca1f6bf..bfe1c3ddf33 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -73,6 +73,12 @@ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank' .col-lg-8 .form-group + = f.label :preferred_language, class: 'label-bold' do + = _('Language') + = f.select :preferred_language, language_choices, {}, class: 'select2' + .form-text.text-muted + = s_('Preferences|This feature is experimental and translations are not complete yet') + .form-group = f.label :first_day_of_week, class: 'label-bold' do = _('First day of the week') = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control' diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 753316b27e2..4d3d92d09c0 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -95,9 +95,6 @@ = f.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)), { help: s_("Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}").html_safe % { learn_more: commit_email_docs_link } }, control_class: 'select2 input-lg' - = f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] }, - { help: s_("Profiles|This feature is experimental and translations are not complete yet") }, - control_class: 'select2 input-lg' = f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username") = f.text_field :linkedin, class: 'input-md', help: s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename") = f.text_field :twitter, class: 'input-md', placeholder: s_("Profiles|@username") diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 0be41b5888c..bba303c906c 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -3,7 +3,7 @@ .project-home-panel{ class: ("empty-project" if empty_repo) } .row.append-bottom-8 .home-panel-title-row.col-md-12.col-lg-6.d-flex - .avatar-container.home-panel-avatar.append-right-default.float-none + .avatar-container.rect-avatar.s64.home-panel-avatar.append-right-default.float-none = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64) .d-flex.flex-column.flex-wrap.align-items-baseline .d-inline-flex.align-items-baseline diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index c64ad1c8147..91c51d5e091 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -53,9 +53,8 @@ = _('Merge request') - if branch.name != @repository.root_ref - = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), + = link_to project_compare_path(@project, @repository.root_ref, branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", - method: :post, title: s_('Branches|Compare') do = s_('Branches|Compare') diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index a58f736b5b4..1a489bfa275 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -46,7 +46,7 @@ %h5.prepend-top-0= _("Project avatar") .form-group - if @project.avatar? - .avatar-container.s160.append-bottom-15 + .avatar-container.rect-avatar.s160.append-bottom-15 = project_icon(@project, alt: '', class: 'avatar project-avatar s160', width: 160, height: 160) - if @project.avatar_in_git %p.light diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml index a69146513d8..3f0798a898d 100644 --- a/app/views/projects/forks/_fork_button.html.haml +++ b/app/views/projects/forks/_fork_button.html.haml @@ -5,7 +5,7 @@ .bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked = link_to project_path(forked_project) do - if /no_((\w*)_)*avatar/.match(avatar) - = group_icon(namespace, class: "avatar s100 identicon") + = group_icon(namespace, class: "avatar rect-avatar s100 identicon") - else .avatar-container.s100 = image_tag(avatar, class: "avatar s100") @@ -18,7 +18,7 @@ class: ("disabled has-tooltip" unless can_create_project), title: (_('You have reached your project limit') unless can_create_project) do - if /no_((\w*)_)*avatar/.match(avatar) - = group_icon(namespace, class: "avatar s100 identicon") + = group_icon(namespace, class: "avatar rect-avatar s100 identicon") - else .avatar-container.s100 = image_tag(avatar, class: "avatar s100") diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 4917f4b8903..42b6aaa2634 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -5,7 +5,7 @@ = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' -%section.js-vue-notes-event +%section.issuable-discussion.js-vue-notes-event #js-vue-notes{ data: { notes_data: notes_data(@issue).to_json, noteable_data: serialize_issuable(@issue), noteable_type: 'Issue', diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 31c72f2f759..ce7c7091c93 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -36,7 +36,7 @@ = issue.due_date.to_s(:medium) - if issue.labels.any? - - issue.labels.each do |label| + - labels_sorted_by_title(issue.labels).each do |label| = link_to_label(label, subject: issue.project, css_class: 'label-link') .issuable-meta diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 653b7d4c6f3..3a674da6e87 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -14,9 +14,12 @@ .detail-page-header-body .issuable-status-box.status-box.status-box-issue-closed{ class: issue_button_visibility(@issue, false) } = sprite_icon('mobile-issue-close', size: 16, css_class: 'd-block d-sm-none') - %span.d-none.d-sm-block + .d-none.d-sm-block - if @issue.moved? - = _("Closed (moved)") + - moved_link_start = "<a href=\"#{issue_path(@issue.moved_to)}\" class=\"text-white text-underline\">".html_safe + - moved_link_end = '</a>'.html_safe + = s_('IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})').html_safe % {moved_link_start: moved_link_start, + moved_link_end: moved_link_end} - else = _("Closed") .issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) } @@ -88,7 +91,6 @@ #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? - %section.issuable-discussion - = render 'projects/issues/discussion' + = render_if_exists 'projects/issues/discussion' = render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index ac29cd8f679..90916191d97 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -34,7 +34,7 @@ = merge_request.target_branch - if merge_request.labels.any? - - merge_request.labels.each do |label| + - labels_sorted_by_title(merge_request.labels).each do |label| = link_to_label(label, subject: merge_request.project, type: :merge_request, css_class: 'label-link') .issuable-meta diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index ae8c801b705..138e2864bad 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -9,4 +9,4 @@ .form-actions = link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove" - else - .nothing-here-block Only the project owner can remove pages + .nothing-here-block Only project maintainers can remove pages diff --git a/app/views/projects/pages/_https_only.html.haml b/app/views/projects/pages/_https_only.html.haml index ce3ef29c32e..74478ee011c 100644 --- a/app/views/projects/pages/_https_only.html.haml +++ b/app/views/projects/pages/_https_only.html.haml @@ -3,7 +3,7 @@ .form-check = f.check_box :pages_https_only, class: 'form-check-input', disabled: pages_https_only_disabled? = f.label :pages_https_only, class: pages_https_only_label_class do - %strong Force domains with SSL certificates to use HTTPS + %strong Force HTTPS (requires valid certificates) - unless pages_https_only_disabled? .prepend-top-10 diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 69a47faabed..9c2efd6aa35 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -23,7 +23,7 @@ - if @pipeline.queued_duration = "(queued for #{time_interval_in_words(@pipeline.queued_duration)})" - .well-segment + .well-segment.qa-pipeline-badges .icon-container = sprite_icon('flag') - if @pipeline.latest? diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml index b36fa9a5f51..2822debe426 100644 --- a/app/views/projects/settings/operations/show.html.haml +++ b/app/views/projects/settings/operations/show.html.haml @@ -1,5 +1,6 @@ - @content_class = 'limit-container-width' unless fluid_layout -- page_title _('Operations') +- page_title _('Operations Settings') +- breadcrumb_title _('Operations Settings') = render 'projects/settings/operations/error_tracking', expanded: true = render_if_exists 'projects/settings/operations/tracing' diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index a4974d89c1a..7682d01a5a1 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,12 +1,16 @@ - page_title _("Snippets") -- if current_user - .top-area - - include_private = @project.team.member?(current_user) || current_user.admin? - = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } +- if @snippets.exists? + - if current_user + .top-area + - include_private = @project.team.member?(current_user) || current_user.admin? + = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } - .nav-controls - if can?(current_user, :create_project_snippet, @project) - = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet") + .nav-controls + - if can?(current_user, :create_project_snippet, @project) + = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet") -= render 'snippets/snippets' + = render 'shared/snippets/list' +- else + = render 'shared/empty_states/snippets', button_path: new_namespace_project_snippet_path(@project.namespace, @project) diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml index 26b333d4ecf..d64e3a49a81 100644 --- a/app/views/projects/snippets/new.html.haml +++ b/app/views/projects/snippets/new.html.haml @@ -1,8 +1,8 @@ - add_to_breadcrumbs _("Snippets"), project_snippets_path(@project) - breadcrumb_title _("New") -- page_title _("New Snippets") +- page_title _("New Snippet") %h3.page-title - = _('New Snippet') + = _("New Snippet") %hr = render "shared/snippets/form", url: project_snippets_path(@project, @snippet) diff --git a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml index 422be28737c..755fd3a17d3 100644 --- a/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml +++ b/app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml @@ -1,5 +1,5 @@ - if show_auto_devops_implicitly_enabled_banner?(project, current_user) - .auto-devops-implicitly-enabled-banner.alert.alert-warning + .qa-auto-devops-banner.auto-devops-implicitly-enabled-banner.alert.alert-warning - more_information_link = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank', class: 'alert-link' - auto_devops_message = s_("AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}") % { more_information_link: more_information_link } = auto_devops_message.html_safe diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml index 6cc8c485666..31a5370a5f8 100644 --- a/app/views/shared/_issuable_meta_data.html.haml +++ b/app/views/shared/_issuable_meta_data.html.haml @@ -1,4 +1,4 @@ -- note_count = @issuable_meta_data[issuable.id].notes_count +- note_count = @issuable_meta_data[issuable.id].user_notes_count - issue_votes = @issuable_meta_data[issuable.id] - upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes - issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes') diff --git a/app/views/shared/empty_states/_snippets.html.haml b/app/views/shared/empty_states/_snippets.html.haml new file mode 100644 index 00000000000..a1a16b9d067 --- /dev/null +++ b/app/views/shared/empty_states/_snippets.html.haml @@ -0,0 +1,20 @@ +- button_path = local_assigns.fetch(:button_path, false) + +.row.empty-state + .col-12 + .svg-content + = image_tag 'illustrations/snippets_empty.svg' + .text-content + - if current_user + %h4 + = s_('SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep.') + %p + = s_('SnippetsEmptyState|They can be either public or private.') + .text-center + = link_to s_('SnippetsEmptyState|New snippet'), button_path, class: 'btn btn-success', title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link' + - unless current_page?(dashboard_snippets_path) + = link_to s_('SnippetsEmptyState|Explore public snippets'), explore_snippets_path, class: 'btn btn-default', title: s_('SnippetsEmptyState|Explore public snippets') + - else + %h4.text-center= s_('SnippetsEmptyState|There are no snippets to show.') + + diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index a1b901aaffa..609b8dce21a 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -14,7 +14,7 @@ %span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) } = visibility_level_icon(group.visibility_level, fw: false) - .avatar-container.s40 + .avatar-container.rect-avatar.s40 = link_to group do = group_icon(group, class: "avatar s40 d-none d-sm-block") .title diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 588659c7e9c..bdba47ed14d 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -128,6 +128,14 @@ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } %button.btn.btn-link{ type: 'button' } = _('No') + #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu + %ul.filter-dropdown{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } } + %button.btn.btn-link{ type: 'button' } + = _('Yes') + %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } + %button.btn.btn-link{ type: 'button' } + = _('No') = render_if_exists 'shared/issuable/filter_weight', type: type diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml index b6ea9185b10..967f31c8325 100644 --- a/app/views/shared/issuable/_sort_dropdown.html.haml +++ b/app/views/shared/issuable/_sort_dropdown.html.haml @@ -5,7 +5,7 @@ .dropdown.inline.prepend-left-10.issue-sort-dropdown .btn-group{ role: 'group' } .btn-group{ role: 'group' } - %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } = sort_title = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index df17ae95e2a..f1a87faa7ac 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -13,14 +13,15 @@ - cache_key = project_list_cache_key(project) - updated_tooltip = time_ago_with_tooltip(project.last_activity_date) - css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between" +- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar' %li.project-row.d-flex{ class: css_class } = cache(cache_key) do - if avatar - .avatar-container.s48.flex-grow-0.flex-shrink-0 + .avatar-container.s48.flex-grow-0.flex-shrink-0{ class: avatar_container_class } = link_to project_path(project), class: dom_class(project) do - if project.creator && use_creator_avatar - = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s65", alt:'' + = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s48", alt:'' - else = project_icon(project, alt: '', class: 'avatar project-avatar s48', width: 48, height: 48) .project-details.d-sm-flex.flex-sm-fill.align-items-center diff --git a/app/views/shared/snippets/_list.html.haml b/app/views/shared/snippets/_list.html.haml new file mode 100644 index 00000000000..5d2152eb411 --- /dev/null +++ b/app/views/shared/snippets/_list.html.haml @@ -0,0 +1,12 @@ +- remote = local_assigns.fetch(:remote, false) +- link_project = local_assigns.fetch(:link_project, false) + +- if @snippets.exists? + .snippets-list-holder + %ul.content-list + = render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project } + + = paginate @snippets, theme: 'gitlab', remote: remote + +- else + .nothing-here-block= s_("SnippetsEmptyState|No snippets found") diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 4f418e2381f..f5fe75bed5a 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -10,4 +10,4 @@ = link_to user_path(@user) do = _("%{user_name} profile page") % { user_name: @user.name } -= render 'snippets' += render 'shared/snippets/list' diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index 55799e10a46..6d7a52c7688 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,5 +1,5 @@ .clearfix - groups.each do |group| = link_to group, class: 'profile-groups-avatars inline', title: group.name do - .avatar-container.s40 + .avatar-container.rect-avatar.s40 = group_icon(group, class: 'avatar group-avatar s40') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 410411b1294..d0fc130b04f 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -101,6 +101,7 @@ - authorized_projects - background_migration +- chat_notification - create_gpg_signature - delete_merged_branches - delete_user diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb index ae853ec9316..adc38226405 100644 --- a/app/workers/build_finished_worker.rb +++ b/app/workers/build_finished_worker.rb @@ -30,5 +30,6 @@ class BuildFinishedWorker # We execute these async as these are independent operations. BuildHooksWorker.perform_async(build.id) ArchiveTraceWorker.perform_async(build.id) + ChatNotificationWorker.perform_async(build.id) if build.pipeline.chat? end end diff --git a/app/workers/chat_notification_worker.rb b/app/workers/chat_notification_worker.rb new file mode 100644 index 00000000000..25a306e94d8 --- /dev/null +++ b/app/workers/chat_notification_worker.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class ChatNotificationWorker + include ApplicationWorker + + RESCHEDULE_INTERVAL = 2.seconds + + # rubocop: disable CodeReuse/ActiveRecord + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + send_response(build) + end + rescue Gitlab::Chat::Output::MissingBuildSectionError + # The creation of traces and sections appears to be eventually consistent. + # As a result it's possible for us to run the above code before the trace + # sections are present. To better handle such cases we'll just reschedule + # the job instead of producing an error. + self.class.perform_in(RESCHEDULE_INTERVAL, build_id) + end + # rubocop: enable CodeReuse/ActiveRecord + + def send_response(build) + Gitlab::Chat::Responder.responder_for(build).try do |responder| + if build.success? + output = Gitlab::Chat::Output.new(build) + + responder.success(output.to_s) + else + responder.failure + end + end + end +end diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb index 148384600b6..2c070d482a1 100644 --- a/app/workers/expire_pipeline_cache_worker.rb +++ b/app/workers/expire_pipeline_cache_worker.rb @@ -37,9 +37,9 @@ class ExpirePipelineCacheWorker Gitlab::Routing.url_helpers.project_new_merge_request_path(project, format: :json) end - def each_pipelines_merge_request_path(project, pipeline) + def each_pipelines_merge_request_path(pipeline) pipeline.all_merge_requests.each do |merge_request| - path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(project, merge_request, format: :json) + path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(merge_request.target_project, merge_request, format: :json) yield(path) end @@ -59,7 +59,7 @@ class ExpirePipelineCacheWorker store.touch(project_pipeline_path(project, pipeline)) store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil? store.touch(new_merge_request_pipelines_path(project)) - each_pipelines_merge_request_path(project, pipeline) do |path| + each_pipelines_merge_request_path(pipeline) do |path| store.touch(path) end end diff --git a/.babelrc.js b/babel.config.js index 1b05a67354e..e3de8ef2d83 100644 --- a/.babelrc.js +++ b/babel.config.js @@ -1,3 +1,5 @@ +/* eslint-disable import/no-commonjs, filenames/match-regex */ + const BABEL_ENV = process.env.BABEL_ENV || process.env.NODE_ENV || null; const presets = [ diff --git a/changelogs/unreleased/19745-forms-with-task-lists-can-be-overwritten-when-editing-simultaneously.yml b/changelogs/unreleased/19745-forms-with-task-lists-can-be-overwritten-when-editing-simultaneously.yml deleted file mode 100644 index b1177e1717e..00000000000 --- a/changelogs/unreleased/19745-forms-with-task-lists-can-be-overwritten-when-editing-simultaneously.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Increase reliability and performance of toggling task items -merge_request: 23938 -author: -type: fixed diff --git a/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml b/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml deleted file mode 100644 index f4a52b1aacd..00000000000 --- a/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add setting for first day of the week -merge_request: 22755 -author: Fabian Schneider @fabsrc -type: added diff --git a/changelogs/unreleased/24680-support-bamboo-api-polymorphism.yml b/changelogs/unreleased/24680-support-bamboo-api-polymorphism.yml deleted file mode 100644 index 5117195cd0c..00000000000 --- a/changelogs/unreleased/24680-support-bamboo-api-polymorphism.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Support bamboo api polymorphism" -merge_request: 24680 -author: Alex Lossent -type: fixed
\ No newline at end of file diff --git a/changelogs/unreleased/24875-label.yml b/changelogs/unreleased/24875-label.yml deleted file mode 100644 index 1f9d2222edf..00000000000 --- a/changelogs/unreleased/24875-label.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Append prioritized label before pagination -merge_request: 24815 -author: -type: fixed diff --git a/changelogs/unreleased/25043-empty-states.yml b/changelogs/unreleased/25043-empty-states.yml deleted file mode 100644 index 529a8b3206f..00000000000 --- a/changelogs/unreleased/25043-empty-states.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make issuable empty states actionable -merge_request: 24077 -author: -type: changed diff --git a/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml b/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml deleted file mode 100644 index 02a667073ca..00000000000 --- a/changelogs/unreleased/25569-changing-wording-to-delete-when-referring-to-removing-a-branch.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use delete instead of remove when referring to `git branch -D` -merge_request: !23966 -author: -type: changed diff --git a/changelogs/unreleased/26375-markdown-footnotes-not-working.yml b/changelogs/unreleased/26375-markdown-footnotes-not-working.yml deleted file mode 100644 index 86adef84a2a..00000000000 --- a/changelogs/unreleased/26375-markdown-footnotes-not-working.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Footnotes now render properly in markdown -merge_request: 24168 -author: -type: fixed diff --git a/changelogs/unreleased/28500-empty-states-for-profile-page.yml b/changelogs/unreleased/28500-empty-states-for-profile-page.yml deleted file mode 100644 index 53f840521ae..00000000000 --- a/changelogs/unreleased/28500-empty-states-for-profile-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refresh empty states for profile page tabs -merge_request: 24549 -author: -type: changed diff --git a/changelogs/unreleased/30120-add-flat-square-badge-style.yml b/changelogs/unreleased/30120-add-flat-square-badge-style.yml deleted file mode 100644 index a542a58d3fc..00000000000 --- a/changelogs/unreleased/30120-add-flat-square-badge-style.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add flat-square badge style -merge_request: 24172 -author: Fabian Schneider @fabsrc -type: added diff --git a/changelogs/unreleased/35638-move-language-setting-to-preferences.yml b/changelogs/unreleased/35638-move-language-setting-to-preferences.yml new file mode 100644 index 00000000000..d8658218676 --- /dev/null +++ b/changelogs/unreleased/35638-move-language-setting-to-preferences.yml @@ -0,0 +1,5 @@ +--- +title: Move language setting to preferences +merge_request: 25427 +author: Fabian Schneider @fabsrc +type: changed diff --git a/changelogs/unreleased/36445-better-indication-that-an-issue-has-been-moved-or-marked-as-duplicated.yml b/changelogs/unreleased/36445-better-indication-that-an-issue-has-been-moved-or-marked-as-duplicated.yml deleted file mode 100644 index 70b561ccbf6..00000000000 --- a/changelogs/unreleased/36445-better-indication-that-an-issue-has-been-moved-or-marked-as-duplicated.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Indicate on Issue Status if an Issue was Moved -merge_request: 24470 -author: -type: added diff --git a/changelogs/unreleased/37990-task-list-bracket.yml b/changelogs/unreleased/37990-task-list-bracket.yml deleted file mode 100644 index ffa77cf0af7..00000000000 --- a/changelogs/unreleased/37990-task-list-bracket.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix ambiguous brackets in task lists -merge_request: 18514 -author: Jared Deckard <jared.deckard@gmail.com> -type: fixed diff --git a/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml b/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml new file mode 100644 index 00000000000..758b97deb3b --- /dev/null +++ b/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml @@ -0,0 +1,5 @@ +--- +title: Add left margin to 1st time contributor badge +merge_request: 25216 +author: Gokhan Apaydin +type: fixed diff --git a/changelogs/unreleased/40795-set-project-name-on-fork-api.yml b/changelogs/unreleased/40795-set-project-name-on-fork-api.yml new file mode 100644 index 00000000000..742184bbe1e --- /dev/null +++ b/changelogs/unreleased/40795-set-project-name-on-fork-api.yml @@ -0,0 +1,5 @@ +--- +title: Add ability to set path and name for project on fork using API +merge_request: 25363 +author: +type: added diff --git a/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml b/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml deleted file mode 100644 index 01036253151..00000000000 --- a/changelogs/unreleased/40997-gitlab-pages-deploy-jobs-have-a-null-status.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix empty labels of CI builds for gitlab-pages on pipeline page -merge_request: 24451 -author: -type: fixed diff --git a/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml b/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml deleted file mode 100644 index 5a4ff8b3358..00000000000 --- a/changelogs/unreleased/42769-remove-expansion-hover-animation-from-status-icon-buttons.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove expansion hover animation from pipeline status icon buttons -merge_request: 24268 -author: Nathan Friend -type: changed diff --git a/changelogs/unreleased/43681-display-last-activity-and-created-at-datetimes-for-users-in-admin-users.yml b/changelogs/unreleased/43681-display-last-activity-and-created-at-datetimes-for-users-in-admin-users.yml deleted file mode 100644 index 0fbf6314a27..00000000000 --- a/changelogs/unreleased/43681-display-last-activity-and-created-at-datetimes-for-users-in-admin-users.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display last activity and created at datetimes for users -merge_request: 24181 -author: -type: added diff --git a/changelogs/unreleased/44332-add-openid-profile-scopes.yml b/changelogs/unreleased/44332-add-openid-profile-scopes.yml deleted file mode 100644 index b554fab5139..00000000000 --- a/changelogs/unreleased/44332-add-openid-profile-scopes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: GitLab now supports the profile and email scopes from OpenID Connect -merge_request: 24335 -author: Goten Xiao -type: added diff --git a/changelogs/unreleased/44698-recaptcha.yml b/changelogs/unreleased/44698-recaptcha.yml deleted file mode 100644 index e1760a6c635..00000000000 --- a/changelogs/unreleased/44698-recaptcha.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent unload when Recaptcha is open -merge_request: 24625 -author: -type: fixed diff --git a/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml b/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml deleted file mode 100644 index b4cba5041d1..00000000000 --- a/changelogs/unreleased/45779-fix-default-visibility-level-for-projects.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix default visibility_level for new projects -merge_request: 24120 -author: Fabian Schneider @fabsrc -type: fixed diff --git a/changelogs/unreleased/45791-number-of-repositories-usage-ping.yml b/changelogs/unreleased/45791-number-of-repositories-usage-ping.yml deleted file mode 100644 index 8d1f5df56ea..00000000000 --- a/changelogs/unreleased/45791-number-of-repositories-usage-ping.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add repositories count to usage ping data -merge_request: 24823 -author: -type: added diff --git a/changelogs/unreleased/46448-add-timestamps-for-each-stage-of-gitlab-rake-gitlab-backup-restore.yml b/changelogs/unreleased/46448-add-timestamps-for-each-stage-of-gitlab-rake-gitlab-backup-restore.yml deleted file mode 100644 index 4ce6787570a..00000000000 --- a/changelogs/unreleased/46448-add-timestamps-for-each-stage-of-gitlab-rake-gitlab-backup-restore.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display timestamps to messages printed by gitlab:backup:restore rake tasks -merge_request: -author: Will Chandler -type: changed diff --git a/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml b/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml new file mode 100644 index 00000000000..d052a28ab51 --- /dev/null +++ b/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml @@ -0,0 +1,5 @@ +--- +title: Sort Environments by Last Updated +merge_request: 25260 +author: +type: added diff --git a/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml b/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml deleted file mode 100644 index 28e2a4cc377..00000000000 --- a/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Redesigned related merge requests in issue page. -merge_request: 24270 -author: -type: changed diff --git a/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml b/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml deleted file mode 100644 index d1a80ab43cf..00000000000 --- a/changelogs/unreleased/47988-improve-milestone-queries-with-subqueries.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve milestone queries using subqueries instead of separate queries for ids -merge_request: 24325 -author: -type: performance diff --git a/changelogs/unreleased/48798-keybinding-mr-diff.yml b/changelogs/unreleased/48798-keybinding-mr-diff.yml new file mode 100644 index 00000000000..3ef3f07f27c --- /dev/null +++ b/changelogs/unreleased/48798-keybinding-mr-diff.yml @@ -0,0 +1,5 @@ +--- +title: Next/previous navigation between files in MR review +merge_request: 25355 +author: +type: added
\ No newline at end of file diff --git a/changelogs/unreleased/50013-add-browser-platform-flags.yml b/changelogs/unreleased/50013-add-browser-platform-flags.yml deleted file mode 100644 index 6176b8b64a7..00000000000 --- a/changelogs/unreleased/50013-add-browser-platform-flags.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add CSS & JS global flags to represent browser and platform -merge_request: 24017 -author: -type: other diff --git a/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml b/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml new file mode 100644 index 00000000000..0188df7fce7 --- /dev/null +++ b/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml @@ -0,0 +1,5 @@ +--- +title: Use auto-build-image for build job in Auto-DevOps.gitlab-ci.yml +merge_request: 24279 +author: +type: changed diff --git a/changelogs/unreleased/50352-sort-save.yml b/changelogs/unreleased/50352-sort-save.yml deleted file mode 100644 index cd046c8b785..00000000000 --- a/changelogs/unreleased/50352-sort-save.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Save issues/merge request sorting options to backend -merge_request: 24198 -author: -type: added diff --git a/changelogs/unreleased/50521-block-emojis-and-symbol-characters-from-user-s-full-names-2.yml b/changelogs/unreleased/50521-block-emojis-and-symbol-characters-from-user-s-full-names-2.yml deleted file mode 100644 index 04caf8262c6..00000000000 --- a/changelogs/unreleased/50521-block-emojis-and-symbol-characters-from-user-s-full-names-2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Block emojis and symbol characters from users full names -merge_request: 24523 -author: -type: other diff --git a/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml b/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml deleted file mode 100644 index cf3d73fce0c..00000000000 --- a/changelogs/unreleased/51754-admin-view-private-personal-snippets.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow users with full private access to read private personal snippets. -merge_request: 24560 -author: -type: fixed diff --git a/changelogs/unreleased/51759-filter-by-language.yml b/changelogs/unreleased/51759-filter-by-language.yml deleted file mode 100644 index 6b5bedd6b2d..00000000000 --- a/changelogs/unreleased/51759-filter-by-language.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add `with_programming_language` filter for projects to API -merge_request: 24377 -author: Dylan MacKenzie -type: added diff --git a/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml b/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml new file mode 100644 index 00000000000..76ea4149c56 --- /dev/null +++ b/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml @@ -0,0 +1,5 @@ +--- +title: Add support for toggling discussion filter from notes section +merge_request: 25426 +author: +type: added diff --git a/changelogs/unreleased/51913-api-getting-projects-for-users-with-dot-gets-404.yml b/changelogs/unreleased/51913-api-getting-projects-for-users-with-dot-gets-404.yml deleted file mode 100644 index 9d72efdd52a..00000000000 --- a/changelogs/unreleased/51913-api-getting-projects-for-users-with-dot-gets-404.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: Support username with dots' -merge_request: 24395 -author: Robert Schilling -type: fixed diff --git a/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml b/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml new file mode 100644 index 00000000000..84062c6db91 --- /dev/null +++ b/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml @@ -0,0 +1,5 @@ +--- +title: 'Timer and action name aligned vertically for delayed jobs in pipeline actions' +merge_request: 25117 +author: Gokhan Apaydin +type: fixed diff --git a/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml b/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml deleted file mode 100644 index c1cde0ceff6..00000000000 --- a/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve In Merge Request diff screen, master is not a hyperlink -merge_request: 23874 -author: -type: fixed diff --git a/changelogs/unreleased/52278-squash-checkbox-fix.yml b/changelogs/unreleased/52278-squash-checkbox-fix.yml deleted file mode 100644 index c81748ae419..00000000000 --- a/changelogs/unreleased/52278-squash-checkbox-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve When merging an MR, the squash checkbox isnt always supported -merge_request: 24296 -author: -type: fixed diff --git a/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml b/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml deleted file mode 100644 index cf1c4378f18..00000000000 --- a/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show MR statistics in diff comparisons -merge_request: !24569 -author: -type: changed diff --git a/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml b/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml deleted file mode 100644 index 07cb35e6529..00000000000 --- a/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Modifies environment scope UI on cluster page -merge_request: 24376 -author: -type: other diff --git a/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml b/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml deleted file mode 100644 index eb4851971fb..00000000000 --- a/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Moves domain setting from Auto DevOps to Cluster's page -merge_request: 24580 -author: -type: added diff --git a/changelogs/unreleased/52568-external-mr-diffs.yml b/changelogs/unreleased/52568-external-mr-diffs.yml deleted file mode 100644 index b1c9d5cb809..00000000000 --- a/changelogs/unreleased/52568-external-mr-diffs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow merge request diffs to be placed into an object store -merge_request: 24276 -author: -type: added diff --git a/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml b/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml deleted file mode 100644 index f79078c1fd9..00000000000 --- a/changelogs/unreleased/52674-api-v4-projects-project_id-jobs-endpoint-hits-statement-timeout.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "[API] Omit `X-Total` and `X-Total-Pages` headers when items count is more than 10,000" -merge_request: 23931 -author: -type: performance diff --git a/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml b/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml new file mode 100644 index 00000000000..9329e81eb83 --- /dev/null +++ b/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml @@ -0,0 +1,5 @@ +--- +title: Add rectangular project and group avatars +merge_request: 25098 +author: +type: other diff --git a/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml b/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml new file mode 100644 index 00000000000..7fa01e2835a --- /dev/null +++ b/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml @@ -0,0 +1,5 @@ +--- +title: Hide pipeline status when pipelines are disabled on project. +merge_request: 25204 +author: +type: fixed diff --git a/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml b/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml deleted file mode 100644 index b661c55957d..00000000000 --- a/changelogs/unreleased/52971-merge-request-file-browser-should-always-be-possible-show-hide.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make possible to toggle file tree while scrolling through diffs -merge_request: !24103 -author: -type: changed diff --git a/changelogs/unreleased/53104-redesign-group-overview-ui-mvc.yml b/changelogs/unreleased/53104-redesign-group-overview-ui-mvc.yml deleted file mode 100644 index cb810b7ac7f..00000000000 --- a/changelogs/unreleased/53104-redesign-group-overview-ui-mvc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refresh group overview to match project overview -merge_request: 23866 -author: -type: changed diff --git a/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml b/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml new file mode 100644 index 00000000000..e0ed38fc2fa --- /dev/null +++ b/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Use limited counter for runner build count in admin page. +merge_request: 25220 +author: +type: fixed diff --git a/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml b/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml new file mode 100644 index 00000000000..32cca07f58d --- /dev/null +++ b/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml @@ -0,0 +1,5 @@ +--- +title: Remove undigested token column from personal_access_tokens table from the database +merge_request: 22743 +author: +type: changed diff --git a/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml b/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml deleted file mode 100644 index 1e9c7f3913c..00000000000 --- a/changelogs/unreleased/53431-fix-upcoming-milestone-filter-for-groups.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix upcoming milestones filter not including group milestones -merge_request: 23098 -author: Heinrich Lee Yu -type: fixed diff --git a/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml b/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml deleted file mode 100644 index 08c5ded05d5..00000000000 --- a/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Redirect GET projects/:id to project page -merge_request: 24467 -author: -type: added diff --git a/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml b/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml deleted file mode 100644 index 12a6509e6f7..00000000000 --- a/changelogs/unreleased/53676-ip-address-of-gitlab-runner-is-wrong-in-the-runners-description.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Get remote IP address of runner -merge_request: 24624 -author: -type: changed diff --git a/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml b/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml deleted file mode 100644 index d804e2df2cd..00000000000 --- a/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix foreground color for labels to ensure consistency of label appearance -merge_request: 23873 -author: Nathan Friend -type: fixed diff --git a/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml b/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml deleted file mode 100644 index 1daa72fb9c4..00000000000 --- a/changelogs/unreleased/53856-changing-group-visibility-does-not-re-enable-save-button.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix suboptimal handling of checkbox and radio input events causing - group general settings submit button to stay disabled after changing its visibility -merge_request: 23022 -author: -type: fixed diff --git a/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml b/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml new file mode 100644 index 00000000000..6c621763e2e --- /dev/null +++ b/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Promote project milestone to a group milestone' +merge_request: 25203 +author: Nermin Vehabovic +type: added diff --git a/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml b/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml deleted file mode 100644 index adaaed7f1aa..00000000000 --- a/changelogs/unreleased/53950-commit-comments-displayed-on-a-merge-request.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display "commented" only for commit discussions on merge requests -merge_request: 24427 -author: -type: changed diff --git a/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml b/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml deleted file mode 100644 index 6fc8aa1a195..00000000000 --- a/changelogs/unreleased/54167-rename-project-tags-to-project-topics.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Rename project tags to project topics -merge_request: 24219 -author: -type: other diff --git a/changelogs/unreleased/54213-standardize-token-value-capitalization-in-filter-bar.yml b/changelogs/unreleased/54213-standardize-token-value-capitalization-in-filter-bar.yml deleted file mode 100644 index 37dea77b8d2..00000000000 --- a/changelogs/unreleased/54213-standardize-token-value-capitalization-in-filter-bar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Standardize filter value capitlization in filter bar in both issues and boards pages -merge_request: 23846 -author: obahareth -type: changed diff --git a/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml b/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml deleted file mode 100644 index d1bdbccb20a..00000000000 --- a/changelogs/unreleased/54250-upstream-kubeclient-redirect-patch.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade kubeclient to 4.2.2 and swap out monkey-patch to disallow redirects -merge_request: 24284 -author: -type: other diff --git a/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml b/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml deleted file mode 100644 index 4d543db567d..00000000000 --- a/changelogs/unreleased/54484-anchor-links-to-comments-or-system-notes-can-break-with-discussion-filters.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Ensured links to a comment or system note anchor resolves to the right note if a user has a discussion filter. -merge_request: 24228 -author: -type: changed diff --git a/changelogs/unreleased/54544-update-project-topics-styling-to-use-badges-design.yml b/changelogs/unreleased/54544-update-project-topics-styling-to-use-badges-design.yml deleted file mode 100644 index de12c66e9ef..00000000000 --- a/changelogs/unreleased/54544-update-project-topics-styling-to-use-badges-design.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update project topics styling to use badges design -merge_request: 24415 -author: -type: changed diff --git a/changelogs/unreleased/54905-milestone-search.yml b/changelogs/unreleased/54905-milestone-search.yml deleted file mode 100644 index 88717242e7c..00000000000 --- a/changelogs/unreleased/54905-milestone-search.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds milestone search -merge_request: 24265 -author: Jacopo Beschi @jacopo-beschi -type: added diff --git a/changelogs/unreleased/54924-refactor-notes-actions-params.yml b/changelogs/unreleased/54924-refactor-notes-actions-params.yml new file mode 100644 index 00000000000..b6083820401 --- /dev/null +++ b/changelogs/unreleased/54924-refactor-notes-actions-params.yml @@ -0,0 +1,5 @@ +--- +title: Fix commenting on commits having SHA1 starting with a large number +merge_request: 25278 +author: +type: fixed diff --git a/changelogs/unreleased/55057-system-message-to-core.yml b/changelogs/unreleased/55057-system-message-to-core.yml new file mode 100644 index 00000000000..3381879eb4a --- /dev/null +++ b/changelogs/unreleased/55057-system-message-to-core.yml @@ -0,0 +1,5 @@ +--- +title: Port System Header and Footer feature to Core +merge_request: 25241 +author: +type: added diff --git a/changelogs/unreleased/55098-ui-bug-adding-group-members-with-lower-permissions.yml b/changelogs/unreleased/55098-ui-bug-adding-group-members-with-lower-permissions.yml deleted file mode 100644 index f22524ef4b2..00000000000 --- a/changelogs/unreleased/55098-ui-bug-adding-group-members-with-lower-permissions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve UI bug adding group members with lower permissions -merge_request: 24820 -author: -type: fixed diff --git a/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml b/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml deleted file mode 100644 index b609fc2d60b..00000000000 --- a/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: Fix default_branch_protection admin setting' -merge_request: 24398 -author: Robert Schilling -type: fixed diff --git a/changelogs/unreleased/55242-skeleton-loading-releases.yml b/changelogs/unreleased/55242-skeleton-loading-releases.yml deleted file mode 100644 index 43cda64ce04..00000000000 --- a/changelogs/unreleased/55242-skeleton-loading-releases.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds skeleton loading to releases page -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml b/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml deleted file mode 100644 index 724de733b7c..00000000000 --- a/changelogs/unreleased/55495-teamcity-use-revision-in-query.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Build number does not need to be tweaked anymore for the TeamCity integration to work properly. -merge_request: 23898 -author: -type: changed diff --git a/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml b/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml deleted file mode 100644 index 071036cd568..00000000000 --- a/changelogs/unreleased/55628-artifacts-from-a-job-defined-after-a-parallel-job-are-not-downloaded.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Handle regular job dependencies next to parallelized job dependencies. -merge_request: 24273 -author: -type: fixed diff --git a/changelogs/unreleased/55703-md-image-borders.yml b/changelogs/unreleased/55703-md-image-borders.yml new file mode 100644 index 00000000000..94297a42f6d --- /dev/null +++ b/changelogs/unreleased/55703-md-image-borders.yml @@ -0,0 +1,5 @@ +--- +title: Only show borders for markdown images in notes +merge_request: 25448 +author: +type: fixed diff --git a/changelogs/unreleased/55820-adds-common-name-chart-value.yml b/changelogs/unreleased/55820-adds-common-name-chart-value.yml deleted file mode 100644 index 1871abbfc6b..00000000000 --- a/changelogs/unreleased/55820-adds-common-name-chart-value.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Ensure Cert Manager works with Auto DevOps URLs greater than 64 bytes -merge_request: 24683 -author: -type: fixed diff --git a/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml b/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml deleted file mode 100644 index 2fbf334f5e9..00000000000 --- a/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Emoji and cancel button are taller than input in set user status modal -merge_request: 24173 -author: Dhiraj Bodicherla -type: fixed diff --git a/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml b/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml new file mode 100644 index 00000000000..ef3d9844acb --- /dev/null +++ b/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml @@ -0,0 +1,5 @@ +--- +title: Review App Link to Changed Page if Only One Change Present +merge_request: 25048 +author: +type: changed diff --git a/changelogs/unreleased/55945-suggested-change-preview-highlight.yml b/changelogs/unreleased/55945-suggested-change-preview-highlight.yml deleted file mode 100644 index 997290a5d50..00000000000 --- a/changelogs/unreleased/55945-suggested-change-preview-highlight.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix syntax highlighting for suggested changes preview -merge_request: 24358 -author: -type: fixed diff --git a/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml b/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml deleted file mode 100644 index 01a162944d3..00000000000 --- a/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent checking protected_ref? for ambiguous refs. -merge_request: 24437 -author: -type: fixed diff --git a/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml b/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml deleted file mode 100644 index 407346bbf22..00000000000 --- a/changelogs/unreleased/56010-user-profile-page-horizonal-whitespace-between-overview-columns-breaks-two-column-layout.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove horizontal whitespace on user profile overview on small breakpoints -merge_request: 24189 -author: -type: other diff --git a/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml b/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml deleted file mode 100644 index e324baa94a3..00000000000 --- a/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: API allows setting the squash commit message when squashing a merge request -merge_request: 24784 -author: -type: added diff --git a/changelogs/unreleased/56019-archived-stuck.yml b/changelogs/unreleased/56019-archived-stuck.yml deleted file mode 100644 index de3698a327b..00000000000 --- a/changelogs/unreleased/56019-archived-stuck.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes z-index and margins of archived alert in job page -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml b/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml deleted file mode 100644 index ff9d4f2c175..00000000000 --- a/changelogs/unreleased/56036-fix-translation-of-in-in-job-details-sidebar.yml +++ /dev/null @@ -1,4 +0,0 @@ -title: Remove multilingual translation from the word "in" in the job details sidebar. -merge_request: 24192 -author: Nathan Friend -type: changed diff --git a/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml b/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml deleted file mode 100644 index 4da14114225..00000000000 --- a/changelogs/unreleased/56110-cluster-kubernetes-api-500-error-on-post-request.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improves restriction of multiple Kubernetes clusters through API -merge_request: 24251 -author: -type: fixed diff --git a/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml b/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml deleted file mode 100644 index 92592290ac4..00000000000 --- a/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update CI YAML param table with include -merge_request: !24309 -author: -type: fixed diff --git a/changelogs/unreleased/56332-exclude-public-group-milestones-from-count.yml b/changelogs/unreleased/56332-exclude-public-group-milestones-from-count.yml deleted file mode 100644 index 50ca9c94173..00000000000 --- a/changelogs/unreleased/56332-exclude-public-group-milestones-from-count.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix counts in milestones dashboard -merge_request: 25230 -author: -type: fixed diff --git a/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml b/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml deleted file mode 100644 index 8a6adef5dae..00000000000 --- a/changelogs/unreleased/56334-runners-ipv6-address-overlaps-other-values.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Runners IPv6 address overlaps other values -merge_request: 24531 -author: -type: fixed diff --git a/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml b/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml deleted file mode 100644 index 7c923422534..00000000000 --- a/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Show CI artifact file size with 3 significant digits on 'browse job artifacts' - page -merge_request: 24387 -author: -type: fixed diff --git a/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml b/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml deleted file mode 100644 index fcfa29977d1..00000000000 --- a/changelogs/unreleased/56371-don-t-check-confidential-issues-for-spam.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Do not run spam checks on confidential issues -merge_request: 24453 -author: -type: fixed diff --git a/changelogs/unreleased/56379-pipeline-stages-job-action-button-icon-is-not-aligned.yml b/changelogs/unreleased/56379-pipeline-stages-job-action-button-icon-is-not-aligned.yml deleted file mode 100644 index ec8a1d9d6ea..00000000000 --- a/changelogs/unreleased/56379-pipeline-stages-job-action-button-icon-is-not-aligned.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Pipeline stages job action button icon is not aligned -merge_request: 24577 -author: -type: fixed diff --git a/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml b/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml deleted file mode 100644 index 3494feb9be1..00000000000 --- a/changelogs/unreleased/56389-remove-unwanted-suggestion-flash-margin.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove unwanted margin above suggested changes. -merge_request: 24419 -author: -type: fixed diff --git a/changelogs/unreleased/56398-fix-cluster-installation-loading-state.yml b/changelogs/unreleased/56398-fix-cluster-installation-loading-state.yml deleted file mode 100644 index 19ff408ddf4..00000000000 --- a/changelogs/unreleased/56398-fix-cluster-installation-loading-state.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix cluster installation processing spinner -merge_request: 24814 -author: -type: fixed diff --git a/changelogs/unreleased/56417-update-helm-to-2-12-2.yml b/changelogs/unreleased/56417-update-helm-to-2-12-2.yml deleted file mode 100644 index f01915c532f..00000000000 --- a/changelogs/unreleased/56417-update-helm-to-2-12-2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update Helm to 2.12.2 to address Helm client vulnerability -merge_request: 24418 -author: Takuya Noguchi -type: security diff --git a/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml b/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml new file mode 100644 index 00000000000..7febe175faf --- /dev/null +++ b/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml @@ -0,0 +1,5 @@ +--- +title: Remove duplicate units from metrics graph +merge_request: 25485 +author: +type: fixed diff --git a/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml b/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml new file mode 100644 index 00000000000..9b7aed82d49 --- /dev/null +++ b/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml @@ -0,0 +1,5 @@ +--- +title: "Implement new arguments `state`, `closed_before` and `closed_after` for `IssuesResolver` in GraphQL" +merge_request: 24910 +author: +type: changed diff --git a/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml b/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml deleted file mode 100644 index 671e204da21..00000000000 --- a/changelogs/unreleased/56507-sh-bump-katex-0.10.0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade KaTeX to version 0.10.0 -merge_request: 24478 -author: Andrew Harmon -type: fixed
\ No newline at end of file diff --git a/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml b/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml deleted file mode 100644 index 388ff1d062a..00000000000 --- a/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Project list UI improvements -merge_request: 24855 -author: -type: other diff --git a/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml b/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml deleted file mode 100644 index 9ef274f3b49..00000000000 --- a/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent Sidekiq arguments over 10 KB in size from being logged to JSON -merge_request: 24493 -author: -type: changed diff --git a/changelogs/unreleased/56556-fix-markdown-table-border.yml b/changelogs/unreleased/56556-fix-markdown-table-border.yml deleted file mode 100644 index 7724f49d4e9..00000000000 --- a/changelogs/unreleased/56556-fix-markdown-table-border.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix markdown table border. -merge_request: 24601 -author: -type: fixed diff --git a/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml b/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml deleted file mode 100644 index 52b2db0e999..00000000000 --- a/changelogs/unreleased/56622-admin-settings-cannot-read-property-addeventlistener-of-null.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Load initUserInternalRegexPlaceholder only when required -merge_request: 24522 -author: -type: fixed diff --git a/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml b/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml deleted file mode 100644 index 1f808850554..00000000000 --- a/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Hashed Storage: `AfterRenameService` was receiving the wrong `old_path` under some circunstances' -merge_request: 24526 -author: -type: fixed diff --git a/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml b/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml new file mode 100644 index 00000000000..3eb9e484647 --- /dev/null +++ b/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml @@ -0,0 +1,5 @@ +--- +title: Fix N+1 query in Issues and MergeRequest API when issuable_metadata is present +merge_request: 25042 +author: Alex Koval +type: other diff --git a/changelogs/unreleased/56764-poor-ui-on-milestone-validation-error-page.yml b/changelogs/unreleased/56764-poor-ui-on-milestone-validation-error-page.yml deleted file mode 100644 index 089ffd47321..00000000000 --- a/changelogs/unreleased/56764-poor-ui-on-milestone-validation-error-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix CSS grid on a new Project/Group Milestone -merge_request: 24614 -author: Takuya Noguchi -type: fixed diff --git a/changelogs/unreleased/56788-unicorn-metric-labels.yml b/changelogs/unreleased/56788-unicorn-metric-labels.yml deleted file mode 100644 index 824c981780c..00000000000 --- a/changelogs/unreleased/56788-unicorn-metric-labels.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Clean up unicorn sampler metric labels -merge_request: 24626 -author: bjk-gitlab -type: fixed diff --git a/changelogs/unreleased/56871-list-issues-error.yml b/changelogs/unreleased/56871-list-issues-error.yml new file mode 100644 index 00000000000..af5585c6b5d --- /dev/null +++ b/changelogs/unreleased/56871-list-issues-error.yml @@ -0,0 +1,5 @@ +--- +title: Display error message when API call to list Sentry issues fails +merge_request: 24936 +author: +type: fixed diff --git a/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml b/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml new file mode 100644 index 00000000000..a7af8994852 --- /dev/null +++ b/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml @@ -0,0 +1,5 @@ +--- +title: Only load syntax highlight CSS of selected theme +merge_request: 25232 +author: +type: performance diff --git a/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml b/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml deleted file mode 100644 index f619a009a63..00000000000 --- a/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Correct spacing for comparison page -merge_request: !24783 -author: -type: fixed diff --git a/changelogs/unreleased/57063-implement-new-arguments-iid-for-issuesresolver-in-graphql.yml b/changelogs/unreleased/57063-implement-new-arguments-iid-for-issuesresolver-in-graphql.yml deleted file mode 100644 index b05ab07e14c..00000000000 --- a/changelogs/unreleased/57063-implement-new-arguments-iid-for-issuesresolver-in-graphql.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add argument iids for issues in GraphQL -merge_request: 24802 -author: -type: added diff --git a/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml b/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml deleted file mode 100644 index 3146d07db3d..00000000000 --- a/changelogs/unreleased/57160-merge-request-tabs-header-is-missing-bottom-border.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Return bottom border on MR Tabs -merge_request: !25198 -author: -type: fixed diff --git a/changelogs/unreleased/57227-absolute-uri-missing-hierarchical-segment.yml b/changelogs/unreleased/57227-absolute-uri-missing-hierarchical-segment.yml deleted file mode 100644 index 3a663ce2132..00000000000 --- a/changelogs/unreleased/57227-absolute-uri-missing-hierarchical-segment.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix potential Addressable::URI::InvalidURIError -merge_request: 24908 -author: -type: fixed diff --git a/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml b/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml new file mode 100644 index 00000000000..5681309cb9e --- /dev/null +++ b/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml @@ -0,0 +1,5 @@ +--- +title: Fix alignment of dropdown icon on issuable on mobile +merge_request: 25205 +author: Takuya Noguchi +type: fixed diff --git a/changelogs/unreleased/57583-sticky-bar.yml b/changelogs/unreleased/57583-sticky-bar.yml deleted file mode 100644 index c592343a409..00000000000 --- a/changelogs/unreleased/57583-sticky-bar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes archived sticky top bar without perfomance bar -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/57589-update-workhorse.yml b/changelogs/unreleased/57589-update-workhorse.yml deleted file mode 100644 index 525913bba4c..00000000000 --- a/changelogs/unreleased/57589-update-workhorse.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update Workhorse to v8.3.1 -merge_request: -author: -type: other diff --git a/changelogs/unreleased/57650-remove-tld-validation-from-cluster.yml b/changelogs/unreleased/57650-remove-tld-validation-from-cluster.yml deleted file mode 100644 index 683b007a8a1..00000000000 --- a/changelogs/unreleased/57650-remove-tld-validation-from-cluster.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes incorrect TLD validation errors for Kubernetes cluster domain -merge_request: 25262 -author: -type: fixed diff --git a/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml b/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml new file mode 100644 index 00000000000..6fb198e1552 --- /dev/null +++ b/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml @@ -0,0 +1,5 @@ +--- +title: Fix project import error importing releases +merge_request: 25495 +author: +type: fixed diff --git a/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml b/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml new file mode 100644 index 00000000000..2775d9f4e36 --- /dev/null +++ b/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml @@ -0,0 +1,5 @@ +--- +title: Add Link from Closed (moved) Issues to Moved Issue +merge_request: 25300 +author: +type: added diff --git a/changelogs/unreleased/57785-create-project-template-for-netlify.yml b/changelogs/unreleased/57785-create-project-template-for-netlify.yml new file mode 100644 index 00000000000..78e9e3dece5 --- /dev/null +++ b/changelogs/unreleased/57785-create-project-template-for-netlify.yml @@ -0,0 +1,5 @@ +--- +title: Resolve Create Project Template for Netlify +merge_request: 25453 +author: +type: changed diff --git a/changelogs/unreleased/57794-project-template-for-net.yml b/changelogs/unreleased/57794-project-template-for-net.yml new file mode 100644 index 00000000000..bc05ac10aff --- /dev/null +++ b/changelogs/unreleased/57794-project-template-for-net.yml @@ -0,0 +1,5 @@ +--- +title: Add Project template for .NET Core +merge_request: 25486 +author: +type: changed diff --git a/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml b/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml new file mode 100644 index 00000000000..046ef8ee99e --- /dev/null +++ b/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml @@ -0,0 +1,5 @@ +--- +title: Fix ETag caching not being used for AJAX requests +merge_request: 25400 +author: +type: fixed diff --git a/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml b/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml new file mode 100644 index 00000000000..7cfeb4a0cd7 --- /dev/null +++ b/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml @@ -0,0 +1,5 @@ +--- +title: Respect the should_remove_source_branch parameter to the merge API +merge_request: 25525 +author: +type: fixed diff --git a/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml b/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml new file mode 100644 index 00000000000..a7a87f60c28 --- /dev/null +++ b/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml @@ -0,0 +1,5 @@ +--- +title: Allow configuring POSTGRES_VERSION in Auto DevOps +merge_request: 25500 +author: +type: added diff --git a/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml b/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml deleted file mode 100644 index cd7b56a1e05..00000000000 --- a/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Creates mixin to reduce code duplication between CE and EE in graph component -merge_request: -author: -type: other diff --git a/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml b/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml new file mode 100644 index 00000000000..c014edf9c09 --- /dev/null +++ b/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml @@ -0,0 +1,5 @@ +--- +title: Allow users to compare branches on a read-only instance +merge_request: 25414 +author: +type: fixed diff --git a/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml b/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml deleted file mode 100644 index 49511294c48..00000000000 --- a/changelogs/unreleased/Projects--dropdown-is-misaligned-on-issue-boards-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Proper align Projects dropdown on issue boards page -merge_request: 24277 -author: Johann Hubert Sonntagbauer -type: fixed diff --git a/changelogs/unreleased/ab-54270-github-iid.yml b/changelogs/unreleased/ab-54270-github-iid.yml deleted file mode 100644 index 1776b0aeb86..00000000000 --- a/changelogs/unreleased/ab-54270-github-iid.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve efficiency of GitHub importer by reducing amount of locks needed. -merge_request: 24102 -author: -type: performance diff --git a/changelogs/unreleased/ac-pages-subgroups.yml b/changelogs/unreleased/ac-pages-subgroups.yml deleted file mode 100644 index ef5a0c1872e..00000000000 --- a/changelogs/unreleased/ac-pages-subgroups.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Pages for subgroups -merge_request: 23505 -author: -type: added diff --git a/changelogs/unreleased/actioncontroller-parameters-deprecations.yml b/changelogs/unreleased/actioncontroller-parameters-deprecations.yml deleted file mode 100644 index ddd15c37542..00000000000 --- a/changelogs/unreleased/actioncontroller-parameters-deprecations.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix several ActionController::Parameters deprecations -merge_request: 24332 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml b/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml deleted file mode 100644 index e200bbaa806..00000000000 --- a/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add badge count to projects -merge_request: 18425 -author: George Tsiolis -type: added diff --git a/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml b/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml deleted file mode 100644 index 7d767e220f7..00000000000 --- a/changelogs/unreleased/add-uniqueness-validation-to-url-column-in-releases-link-model.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add uniqueness validation to url column in Releases::Link model -merge_request: 24223 -author: -type: other diff --git a/changelogs/unreleased/add-youtrack-integration.yml b/changelogs/unreleased/add-youtrack-integration.yml new file mode 100644 index 00000000000..f500e625145 --- /dev/null +++ b/changelogs/unreleased/add-youtrack-integration.yml @@ -0,0 +1,5 @@ +--- +title: Add YouTrack integration service +merge_request: 25361 +author: Yauhen Kotau @bessorion +type: added diff --git a/changelogs/unreleased/adrianmoisey-GITLAB_PAGES_PREDEFINED_VARIABLES.yml b/changelogs/unreleased/adrianmoisey-GITLAB_PAGES_PREDEFINED_VARIABLES.yml deleted file mode 100644 index a664c44e1d7..00000000000 --- a/changelogs/unreleased/adrianmoisey-GITLAB_PAGES_PREDEFINED_VARIABLES.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add GitLab Pages predefined CI variables 'CI_PAGES_DOMAIN' and 'CI_PAGES_URL' -merge_request: 24504 -author: Adrian Moisey -type: added diff --git a/changelogs/unreleased/adriel-remove-feature-flag.yml b/changelogs/unreleased/adriel-remove-feature-flag.yml deleted file mode 100644 index d442e120d60..00000000000 --- a/changelogs/unreleased/adriel-remove-feature-flag.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update metrics dashboard graph design -merge_request: 24653 -author: -type: changed diff --git a/changelogs/unreleased/allow-maintainers-to-remove-pages.yml b/changelogs/unreleased/allow-maintainers-to-remove-pages.yml new file mode 100644 index 00000000000..6e344dbe0e9 --- /dev/null +++ b/changelogs/unreleased/allow-maintainers-to-remove-pages.yml @@ -0,0 +1,5 @@ +--- +title: Allow maintainers to remove pages +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml b/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml deleted file mode 100644 index 5365260cbae..00000000000 --- a/changelogs/unreleased/an-dtracing-test-for-invalid-tracers.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid overwriting default jaeger values with nil -merge_request: 24482 -author: -type: fixed diff --git a/changelogs/unreleased/an-gilab-process-name.yml b/changelogs/unreleased/an-gilab-process-name.yml deleted file mode 100644 index 72d811ee21f..00000000000 --- a/changelogs/unreleased/an-gilab-process-name.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Extract process_name from GitLab::Sentry -merge_request: 24422 -author: -type: other diff --git a/changelogs/unreleased/an-opentracing-active-record-tracing.yml b/changelogs/unreleased/an-opentracing-active-record-tracing.yml deleted file mode 100644 index 59b480675ec..00000000000 --- a/changelogs/unreleased/an-opentracing-active-record-tracing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds tracing support for ActiveRecord notifications -merge_request: 24604 -author: -type: other diff --git a/changelogs/unreleased/an-opentracing-factory.yml b/changelogs/unreleased/an-opentracing-factory.yml deleted file mode 100644 index c04736f3e63..00000000000 --- a/changelogs/unreleased/an-opentracing-factory.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Conditionally initialize the global opentracing tracer -merge_request: 24186 -author: -type: other diff --git a/changelogs/unreleased/an-opentracing-propagation.yml b/changelogs/unreleased/an-opentracing-propagation.yml deleted file mode 100644 index d9aa7cd0048..00000000000 --- a/changelogs/unreleased/an-opentracing-propagation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds inter-service OpenTracing propagation -merge_request: 24239 -author: -type: other diff --git a/changelogs/unreleased/an-opentracing-render-tracing.yml b/changelogs/unreleased/an-opentracing-render-tracing.yml deleted file mode 100644 index 6ff7f1f3cf2..00000000000 --- a/changelogs/unreleased/an-opentracing-render-tracing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add OpenTracing instrumentation for Action View Render events -merge_request: 24728 -author: -type: other diff --git a/changelogs/unreleased/api-group-labels.yml b/changelogs/unreleased/api-group-labels.yml deleted file mode 100644 index 0df6f15a9b6..00000000000 --- a/changelogs/unreleased/api-group-labels.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: Add support for group labels' -merge_request: 21368 -author: Robert Schilling -type: added diff --git a/changelogs/unreleased/api-nested-group-permission.yml b/changelogs/unreleased/api-nested-group-permission.yml deleted file mode 100644 index 3ec0df6893f..00000000000 --- a/changelogs/unreleased/api-nested-group-permission.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Return the maximum group access level in the projects API -merge_request: 24403 -author: -type: changed diff --git a/changelogs/unreleased/api-tags-search.yml b/changelogs/unreleased/api-tags-search.yml deleted file mode 100644 index 1501acd5a9e..00000000000 --- a/changelogs/unreleased/api-tags-search.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: Support searching for tags' -merge_request: 24385 -author: Robert Schilling -type: added diff --git a/changelogs/unreleased/api-wiki-dot-slug.yml b/changelogs/unreleased/api-wiki-dot-slug.yml deleted file mode 100644 index 82c76fa7450..00000000000 --- a/changelogs/unreleased/api-wiki-dot-slug.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: Support dots in wiki slugs' -merge_request: 24383 -author: Robert Schilling -type: fixed diff --git a/changelogs/unreleased/auto-devops-custom-domains.yml b/changelogs/unreleased/auto-devops-custom-domains.yml deleted file mode 100644 index 37e8ee26a4d..00000000000 --- a/changelogs/unreleased/auto-devops-custom-domains.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added support for custom hosts/domains to Auto DevOps -merge_request: 24248 -author: walkafwalka -type: added diff --git a/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml b/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml deleted file mode 100644 index 1a8cdead4ac..00000000000 --- a/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bump kubectl in Auto DevOps to 1.11.6 -merge_request: 24176 -author: -type: other diff --git a/changelogs/unreleased/backup_aws_sse-c.yml b/changelogs/unreleased/backup_aws_sse-c.yml deleted file mode 100644 index 78b57d7efc3..00000000000 --- a/changelogs/unreleased/backup_aws_sse-c.yml +++ /dev/null @@ -1,5 +0,0 @@ -title: Add support for customer provided encryption keys for Amazon S3 remote backups -merge_request: 23797 -author: Pepijn Van Eeckhoudt -type: added - diff --git a/changelogs/unreleased/backup_restore_fix_issue_46891.yml b/changelogs/unreleased/backup_restore_fix_issue_46891.yml deleted file mode 100644 index b8fe3b1b861..00000000000 --- a/changelogs/unreleased/backup_restore_fix_issue_46891.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Modify file restore to rectify tar issue -merge_request: 24000 -author: -type: fixed diff --git a/changelogs/unreleased/bump-ingress-chart-112.yml b/changelogs/unreleased/bump-ingress-chart-112.yml deleted file mode 100644 index 8a46fedb4b0..00000000000 --- a/changelogs/unreleased/bump-ingress-chart-112.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bump nginx-ingress chart to 1.1.2 -merge_request: 24203 -author: -type: other diff --git a/changelogs/unreleased/bvl-fix-race-condition-creating-signature.yml b/changelogs/unreleased/bvl-fix-race-condition-creating-signature.yml deleted file mode 100644 index 307b4f526bb..00000000000 --- a/changelogs/unreleased/bvl-fix-race-condition-creating-signature.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid race conditions when creating GpgSignature -merge_request: 24939 -author: -type: fixed diff --git a/changelogs/unreleased/change-badges-example-to-pipeline.yml b/changelogs/unreleased/change-badges-example-to-pipeline.yml new file mode 100644 index 00000000000..8ed4d77fd6c --- /dev/null +++ b/changelogs/unreleased/change-badges-example-to-pipeline.yml @@ -0,0 +1,5 @@ +--- +title: Change badges.svg example to pipeline.svg +merge_request: 25157 +author: Aviad Levy +type: fixed diff --git a/changelogs/unreleased/chore-update-js-regex.yml b/changelogs/unreleased/chore-update-js-regex.yml deleted file mode 100644 index d45d0b47457..00000000000 --- a/changelogs/unreleased/chore-update-js-regex.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade js-regex gem to version 3.1 -merge_request: 24433 -author: rroger -type: changed diff --git a/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml b/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml deleted file mode 100644 index 6e8dac97249..00000000000 --- a/changelogs/unreleased/cleanup-leagcy-artifact-migration.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Cleanup legacy artifact background migration -merge_request: 24144 -author: -type: other diff --git a/changelogs/unreleased/cluster_application_version_updated.yml b/changelogs/unreleased/cluster_application_version_updated.yml deleted file mode 100644 index 34fe55dcc5e..00000000000 --- a/changelogs/unreleased/cluster_application_version_updated.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update cluster application version on updated and installed status -merge_request: 24810 -author: -type: other diff --git a/changelogs/unreleased/cluster_status_for_ugprading.yml b/changelogs/unreleased/cluster_status_for_ugprading.yml deleted file mode 100644 index ca1f8b3a786..00000000000 --- a/changelogs/unreleased/cluster_status_for_ugprading.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose version for each application in cluster_status JSON endpoint -merge_request: 24791 -author: -type: other diff --git a/changelogs/unreleased/consistent-pagination.yml b/changelogs/unreleased/consistent-pagination.yml new file mode 100644 index 00000000000..95eefaeb31d --- /dev/null +++ b/changelogs/unreleased/consistent-pagination.yml @@ -0,0 +1,5 @@ +--- +title: Fix inconsistent pagination styles +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/container-repository-cleanup-api.yml b/changelogs/unreleased/container-repository-cleanup-api.yml deleted file mode 100644 index c2b23a9add0..00000000000 --- a/changelogs/unreleased/container-repository-cleanup-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Container Registry API with cleanup function -merge_request: 24303 -author: -type: added diff --git a/changelogs/unreleased/custom-helm-chart-repo.yml b/changelogs/unreleased/custom-helm-chart-repo.yml deleted file mode 100644 index 592d2f60ca2..00000000000 --- a/changelogs/unreleased/custom-helm-chart-repo.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added feature to specify a custom Auto DevOps chart repository -merge_request: 24162 -author: walkafwalka -type: added diff --git a/changelogs/unreleased/deprecated-force-reload.yml b/changelogs/unreleased/deprecated-force-reload.yml deleted file mode 100644 index 2a0e97089e0..00000000000 --- a/changelogs/unreleased/deprecated-force-reload.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: 'Fix deprecation: Passing an argument to force an association to reload is - now deprecated' -merge_request: 24136 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/diff-file-finder.yml b/changelogs/unreleased/diff-file-finder.yml deleted file mode 100644 index 3160e9fc91b..00000000000 --- a/changelogs/unreleased/diff-file-finder.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added fuzzy file finder to merge requests -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/diff-tree-collapse-directories.yml b/changelogs/unreleased/diff-tree-collapse-directories.yml deleted file mode 100644 index 6eae48f2352..00000000000 --- a/changelogs/unreleased/diff-tree-collapse-directories.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Collapse directory structure in merge request file tree -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/dm-copy-suggestion-as-gfm.yml b/changelogs/unreleased/dm-copy-suggestion-as-gfm.yml deleted file mode 100644 index 96115e6ade1..00000000000 --- a/changelogs/unreleased/dm-copy-suggestion-as-gfm.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow suggestions to be copied and pasted as GFM -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml b/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml deleted file mode 100644 index 1e1fa8295c3..00000000000 --- a/changelogs/unreleased/dm-trim-discussion-truncated-line-first-chars.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix bug that caused Suggestion Markdown toolbar button to insert snippet with leading +/-/<space> -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml b/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml deleted file mode 100644 index 4539a9b7985..00000000000 --- a/changelogs/unreleased/docs-push-mirror-GitLab-GitHub.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Updated docs for fields in pushing mirror from GitLab to GitHub -merge_request: 24566 -author: Joseph Yu -type: other diff --git a/changelogs/unreleased/dz-sort-labels-alphabetically.yml b/changelogs/unreleased/dz-sort-labels-alphabetically.yml new file mode 100644 index 00000000000..acfde3de999 --- /dev/null +++ b/changelogs/unreleased/dz-sort-labels-alphabetically.yml @@ -0,0 +1,5 @@ +--- +title: Sort labels alphabetically on issues and merge requests list +merge_request: 25470 +author: +type: changed diff --git a/changelogs/unreleased/expire-job-artifacts-worker.yml b/changelogs/unreleased/expire-job-artifacts-worker.yml deleted file mode 100644 index cda6e9ff497..00000000000 --- a/changelogs/unreleased/expire-job-artifacts-worker.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Efficiently remove expired artifacts in `ExpireBuildArtifactsWorker` -merge_request: 24450 -author: -type: performance diff --git a/changelogs/unreleased/expose-merge-ref-to-runner.yml b/changelogs/unreleased/expose-merge-ref-to-runner.yml new file mode 100644 index 00000000000..945f4f6e05a --- /dev/null +++ b/changelogs/unreleased/expose-merge-ref-to-runner.yml @@ -0,0 +1,5 @@ +--- +title: Expose refspecs and depth to runner +merge_request: 25233 +author: +type: added diff --git a/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml b/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml deleted file mode 100644 index b224cace4bf..00000000000 --- a/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Document graphicsmagick installation for source installation -merge_request: 24404 -author: Alexis Reigel -type: added diff --git a/changelogs/unreleased/filter-confidential-issues.yml b/changelogs/unreleased/filter-confidential-issues.yml new file mode 100644 index 00000000000..83f19a57aab --- /dev/null +++ b/changelogs/unreleased/filter-confidential-issues.yml @@ -0,0 +1,5 @@ +--- +title: Ability to filter confidential issues +merge_request: 24960 +author: Robert Schilling +type: added diff --git a/changelogs/unreleased/fix-39759-new-project-icon-vertical-align.yml b/changelogs/unreleased/fix-39759-new-project-icon-vertical-align.yml deleted file mode 100644 index 3d87807dbc1..00000000000 --- a/changelogs/unreleased/fix-39759-new-project-icon-vertical-align.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adjust vertical alignment for project visibility icons -merge_request: 24511 -author: Martin Hobert -type: fixed diff --git a/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml b/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml deleted file mode 100644 index eda69b32094..00000000000 --- a/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show the correct error page when access is denied -merge_request: 23932 -author: -type: fixed diff --git a/changelogs/unreleased/fix-49388.yml b/changelogs/unreleased/fix-49388.yml deleted file mode 100644 index f8b5e3e1943..00000000000 --- a/changelogs/unreleased/fix-49388.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update metrics environment dropdown to show complete option set -merge_request: 24441 -author: -type: fixed diff --git a/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml b/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml deleted file mode 100644 index e33699a2112..00000000000 --- a/changelogs/unreleased/fix-55956-oversized-dropdown-button-custom-notifications.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed oversized custom project notification selector dropdown -merge_request: 24557 -author: -type: fixed diff --git a/changelogs/unreleased/fix-56558-move-primary-button.yml b/changelogs/unreleased/fix-56558-move-primary-button.yml deleted file mode 100644 index 4dcc896b327..00000000000 --- a/changelogs/unreleased/fix-56558-move-primary-button.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Moved primary button for labels to follow the design patterns used on rest of the site -merge_request: -author: Martin Hobert -type: fixed diff --git a/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml b/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml deleted file mode 100644 index bb0b193a846..00000000000 --- a/changelogs/unreleased/fix-auto-devops-domain-title-on-admin-settings.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes Auto DevOps title on CI/CD admin settings -merge_request: 24249 -author: -type: other diff --git a/changelogs/unreleased/fix-badges-logs.yml b/changelogs/unreleased/fix-badges-logs.yml new file mode 100644 index 00000000000..6236e7b046d --- /dev/null +++ b/changelogs/unreleased/fix-badges-logs.yml @@ -0,0 +1,5 @@ +--- +title: Doc - fix the url of pipeline status badge +merge_request: 25404 +author: Aviad Levy +type: fixed diff --git a/changelogs/unreleased/fix-repo-settings-file-upload-error.yml b/changelogs/unreleased/fix-repo-settings-file-upload-error.yml deleted file mode 100644 index b219fdfaa1e..00000000000 --- a/changelogs/unreleased/fix-repo-settings-file-upload-error.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix bug causing repository mirror settings UI to break -merge_request: 23712 -author: -type: fixed diff --git a/changelogs/unreleased/fix_jira_integration_VCS1019.yml b/changelogs/unreleased/fix_jira_integration_VCS1019.yml deleted file mode 100644 index 3582ec1fe0f..00000000000 --- a/changelogs/unreleased/fix_jira_integration_VCS1019.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Jira Service password validation on project integration services. -merge_request: 24896 -author: Daniel Juarez -type: fixed diff --git a/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml b/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml deleted file mode 100644 index f64b29644b0..00000000000 --- a/changelogs/unreleased/fj-55882-fix-files-api-content-disposition.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix files/blob api endpoints content disposition -merge_request: 24267 -author: -type: fixed diff --git a/changelogs/unreleased/force-redeploy-on-updated-secrets.yml b/changelogs/unreleased/force-redeploy-on-updated-secrets.yml deleted file mode 100644 index 3b727c99dd5..00000000000 --- a/changelogs/unreleased/force-redeploy-on-updated-secrets.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Redeploy Auto DevOps deployment on variable updates -merge_request: 24498 -author: walkafwalka -type: added diff --git a/changelogs/unreleased/gitaly-update-1-13-0.yml b/changelogs/unreleased/gitaly-update-1-13-0.yml deleted file mode 100644 index 73de25a532d..00000000000 --- a/changelogs/unreleased/gitaly-update-1-13-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade Gitaly to 1.13.0 -merge_request: 24429 -author: -type: other diff --git a/changelogs/unreleased/gitaly-update-1.18.0.yml b/changelogs/unreleased/gitaly-update-1.18.0.yml deleted file mode 100644 index 392527f5e5d..00000000000 --- a/changelogs/unreleased/gitaly-update-1.18.0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade gitaly to 1.18.0 -merge_request: 24981 -author: -type: other diff --git a/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml b/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml deleted file mode 100644 index 1e0160c4d40..00000000000 --- a/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade gitlab-workhorse to 8.1.0 -merge_request: 24571 -author: -type: other diff --git a/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml b/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml new file mode 100644 index 00000000000..b8668d338de --- /dev/null +++ b/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml @@ -0,0 +1,5 @@ +--- +title: Bump Helm and kubectl used in Kubernetes integration to 2.12.3 and 1.11.7 respectively +merge_request: 25268 +author: +type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-clusters.yml b/changelogs/unreleased/gt-externalize-app-views-clusters.yml deleted file mode 100644 index 6d2284ead37..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-clusters.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/clusters` -merge_request: 24666 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-email_rejection_mailer.yml b/changelogs/unreleased/gt-externalize-app-views-email_rejection_mailer.yml deleted file mode 100644 index 8f6fbdceb54..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-email_rejection_mailer.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/email_rejection_mailer` -merge_request: 24869 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-instance_statistics.yml b/changelogs/unreleased/gt-externalize-app-views-instance_statistics.yml deleted file mode 100644 index a3bf54a1339..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-instance_statistics.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/instance_statistics` -merge_request: 24809 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml b/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml deleted file mode 100644 index ecc878ab892..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-projects-ci.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/projects/ci` -merge_request: 24617 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-milestones.yml b/changelogs/unreleased/gt-externalize-app-views-projects-milestones.yml deleted file mode 100644 index 56aaac812bb..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-projects-milestones.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/projects/milestones` -merge_request: 24726 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-pages_domains.yml b/changelogs/unreleased/gt-externalize-app-views-projects-pages_domains.yml deleted file mode 100644 index f60776a2ed8..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-projects-pages_domains.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/projects/pages_domains` -merge_request: 24723 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml b/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml deleted file mode 100644 index 1acea10fcaa..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/projects/project_members` -merge_request: 23227 -author: Tao Wang -type: other diff --git a/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml b/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml deleted file mode 100644 index e77b5376fa8..00000000000 --- a/changelogs/unreleased/gt-externalize-app-views-sent_notifications.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings from `/app/views/sent_notifications` -merge_request: 24576 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-remove-unused-button-class.yml b/changelogs/unreleased/gt-remove-unused-button-class.yml deleted file mode 100644 index f7889e1d6f6..00000000000 --- a/changelogs/unreleased/gt-remove-unused-button-class.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove unused button classes `btn-create` and `comment-btn` -merge_request: 23232 -author: George Tsiolis -type: performance diff --git a/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml b/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml deleted file mode 100644 index b612bb3ee39..00000000000 --- a/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove all `$theme-gray-{weight}` variables in favor of `$gray-{weight}` -merge_request: 24333 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml b/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml new file mode 100644 index 00000000000..f7b10ea5c17 --- /dev/null +++ b/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml @@ -0,0 +1,5 @@ +--- +title: Update operations settings breadcrumb trail +merge_request: 25539 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml b/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml deleted file mode 100644 index fa06a78adae..00000000000 --- a/changelogs/unreleased/gt-update-string-struture-for-group-runners.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update string structure for available group runners -merge_request: 24187 -author: George Tsiolis -type: changed diff --git a/changelogs/unreleased/helm-2-12-3.yml b/changelogs/unreleased/helm-2-12-3.yml new file mode 100644 index 00000000000..0d0d904a9cb --- /dev/null +++ b/changelogs/unreleased/helm-2-12-3.yml @@ -0,0 +1,5 @@ +--- +title: Bump Helm and kubectl in Auto DevOps to 2.12.3 and 1.11.7 respectively +merge_request: 25072 +author: +type: other diff --git a/changelogs/unreleased/hnk-master-patch-61932.yml b/changelogs/unreleased/hnk-master-patch-61932.yml deleted file mode 100644 index 8cc9d0057a9..00000000000 --- a/changelogs/unreleased/hnk-master-patch-61932.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update runner admin page to make description field larger -merge_request: 23593 -author: Sascha Reynolds -type: fixed diff --git a/changelogs/unreleased/homepage-proj-descr-cutoff.yml b/changelogs/unreleased/homepage-proj-descr-cutoff.yml deleted file mode 100644 index 837c01f6722..00000000000 --- a/changelogs/unreleased/homepage-proj-descr-cutoff.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Increase line height of project summaries -merge_request: -author: gfyoung -type: fixed diff --git a/changelogs/unreleased/improve-snippets-empty-state.yml b/changelogs/unreleased/improve-snippets-empty-state.yml new file mode 100644 index 00000000000..9859243a81f --- /dev/null +++ b/changelogs/unreleased/improve-snippets-empty-state.yml @@ -0,0 +1,5 @@ +--- +title: Improve snippets empty state +merge_request: 18348 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/introduce-environment-search-endpoint.yml b/changelogs/unreleased/introduce-environment-search-endpoint.yml deleted file mode 100644 index 01851ba7d27..00000000000 --- a/changelogs/unreleased/introduce-environment-search-endpoint.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Introduce Internal API for searching environment names -merge_request: 24923 -author: -type: added diff --git a/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml b/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml deleted file mode 100644 index 8025cd472bd..00000000000 --- a/changelogs/unreleased/iss-32584-preserve-line-number-fragment-after-redirect.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix lost line number when navigating to a specific line in a protected file - before authenticating. -merge_request: 19165 -author: Scott Escue -type: fixed diff --git a/changelogs/unreleased/issue_55744.yml b/changelogs/unreleased/issue_55744.yml deleted file mode 100644 index 6a643732b18..00000000000 --- a/changelogs/unreleased/issue_55744.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix template labels not being created on new projects -merge_request: 24803 -author: -type: fixed diff --git a/changelogs/unreleased/jc-fix-set-project-writable.yml b/changelogs/unreleased/jc-fix-set-project-writable.yml new file mode 100644 index 00000000000..0bfd90c3967 --- /dev/null +++ b/changelogs/unreleased/jc-fix-set-project-writable.yml @@ -0,0 +1,5 @@ +--- +title: Fix method to mark a project repository as writable +merge_request: 25546 +author: +type: fixed diff --git a/changelogs/unreleased/jej-avoid-csrf-check-on-saml-failure.yml b/changelogs/unreleased/jej-avoid-csrf-check-on-saml-failure.yml deleted file mode 100644 index 18cced2906a..00000000000 --- a/changelogs/unreleased/jej-avoid-csrf-check-on-saml-failure.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display SAML failure messages instead of expecting CSRF token -merge_request: 24509 -author: -type: fixed diff --git a/changelogs/unreleased/jlenny-AddPagesTemplates.yml b/changelogs/unreleased/jlenny-AddPagesTemplates.yml deleted file mode 100644 index 0985e4e18ed..00000000000 --- a/changelogs/unreleased/jlenny-AddPagesTemplates.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add templates for most popular Pages templates -merge_request: 24906 -author: -type: added diff --git a/changelogs/unreleased/jlenny-NewAndroidTemplate.yml b/changelogs/unreleased/jlenny-NewAndroidTemplate.yml deleted file mode 100644 index ae8c58da859..00000000000 --- a/changelogs/unreleased/jlenny-NewAndroidTemplate.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add template for Android with Fastlane -merge_request: 24722 -author: -type: changed diff --git a/changelogs/unreleased/jprovazn-remove-redcarpet.yml b/changelogs/unreleased/jprovazn-remove-redcarpet.yml deleted file mode 100644 index 4e12de2d19b..00000000000 --- a/changelogs/unreleased/jprovazn-remove-redcarpet.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Removed deprecated Redcarpet markdown engine. -merge_request: -author: -type: removed diff --git a/changelogs/unreleased/knative-list.yml b/changelogs/unreleased/knative-list.yml deleted file mode 100644 index 754d8e172cf..00000000000 --- a/changelogs/unreleased/knative-list.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Modified Knative list view to provide more details -merge_request: 24072 -author: Chris Baumbauer -type: changed diff --git a/changelogs/unreleased/knative-show-page.yml b/changelogs/unreleased/knative-show-page.yml deleted file mode 100644 index a48b754940f..00000000000 --- a/changelogs/unreleased/knative-show-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Knative detailed view -merge_request: 23863 -author: Chris Baumbauer -type: added diff --git a/changelogs/unreleased/local-markdown-version-bkp3.yml b/changelogs/unreleased/local-markdown-version-bkp3.yml deleted file mode 100644 index ce5bff6ae6b..00000000000 --- a/changelogs/unreleased/local-markdown-version-bkp3.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow admins to invalidate markdown texts by setting local markdown version. -merge_request: -author: -type: added diff --git a/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml b/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml deleted file mode 100644 index 932850cc825..00000000000 --- a/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix cluster page non-interactive on form validation error -merge_request: 24583 -author: -type: fixed diff --git a/changelogs/unreleased/monospace-registry-tags.yml b/changelogs/unreleased/monospace-registry-tags.yml deleted file mode 100644 index b5992707d8c..00000000000 --- a/changelogs/unreleased/monospace-registry-tags.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use monospace font for registry table tag id and tag name -merge_request: 24205 -author: -type: other diff --git a/changelogs/unreleased/move-job-cancel-btn.yml b/changelogs/unreleased/move-job-cancel-btn.yml deleted file mode 100644 index 41f8e1be5f8..00000000000 --- a/changelogs/unreleased/move-job-cancel-btn.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move cancel & new issue button on job page -merge_request: 24074 -author: -type: changed diff --git a/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml b/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml deleted file mode 100644 index 9e979b48ad1..00000000000 --- a/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move permission check of manual actions of deployments -merge_request: 24660 -author: -type: other diff --git a/changelogs/unreleased/move_chatops_to_core.yml b/changelogs/unreleased/move_chatops_to_core.yml new file mode 100644 index 00000000000..7a75efedfa8 --- /dev/null +++ b/changelogs/unreleased/move_chatops_to_core.yml @@ -0,0 +1,5 @@ +--- +title: Move ChatOps to Core +merge_request: 24780 +author: +type: changed diff --git a/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml b/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml deleted file mode 100644 index b01962591c6..00000000000 --- a/changelogs/unreleased/mr-file-tree-blob-truncate-improvements.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add folder header to files in merge request tree list -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/mr-rebase-failing-tests.yml b/changelogs/unreleased/mr-rebase-failing-tests.yml deleted file mode 100644 index 07ae05766b1..00000000000 --- a/changelogs/unreleased/mr-rebase-failing-tests.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed rebase button not showing in merge request widget -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/not-run-pipeline-on-empty-merge-request.yml b/changelogs/unreleased/not-run-pipeline-on-empty-merge-request.yml deleted file mode 100644 index 732e4baf4e9..00000000000 --- a/changelogs/unreleased/not-run-pipeline-on-empty-merge-request.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Don't create new merge request pipeline without commits -merge_request: 24503 -author: Hiroyuki Sato -type: added diff --git a/changelogs/unreleased/notebook-multiple-outputs.yml b/changelogs/unreleased/notebook-multiple-outputs.yml deleted file mode 100644 index 38cc52c0634..00000000000 --- a/changelogs/unreleased/notebook-multiple-outputs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Support multiple outputs in jupyter notebooks -merge_request: -author: -type: changed diff --git a/changelogs/unreleased/notes-awards-double-tooltip-fix.yml b/changelogs/unreleased/notes-awards-double-tooltip-fix.yml deleted file mode 100644 index 23338a60c2a..00000000000 --- a/changelogs/unreleased/notes-awards-double-tooltip-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed double tooltips on note awards buttons -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml b/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml new file mode 100644 index 00000000000..012b547a630 --- /dev/null +++ b/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml @@ -0,0 +1,5 @@ +--- +title: Support merge ref writing (without merging to target branch) +merge_request: 24692 +author: +type: added diff --git a/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml b/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml deleted file mode 100644 index 6a2a67e7aa8..00000000000 --- a/changelogs/unreleased/osw-enforces-project-removal-with-past-failed-attempts.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Cleanup stale +deleted repo paths on project removal (adjusts project removal bug) -merge_request: 24269 -author: -type: fixed diff --git a/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml b/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml deleted file mode 100644 index b2ac53312ae..00000000000 --- a/changelogs/unreleased/osw-fix-bottom-expansion-diff-comment.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adjusts duplicated line when commenting on unfolded diff lines (in the bottom) -merge_request: 24201 -author: -type: fixed diff --git a/changelogs/unreleased/patch-38.yml b/changelogs/unreleased/patch-38.yml deleted file mode 100644 index 9179fc6846e..00000000000 --- a/changelogs/unreleased/patch-38.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: fix display comment avatars issue in IE 11 -merge_request: 24777 -author: Gokhan Apaydin -type: fixed diff --git a/changelogs/unreleased/patch-45.yml b/changelogs/unreleased/patch-45.yml new file mode 100644 index 00000000000..94fa1d29b32 --- /dev/null +++ b/changelogs/unreleased/patch-45.yml @@ -0,0 +1,5 @@ +--- +title: Fix incorrect Pages Domains checkbox description. +merge_request: 25392 +author: Anton Melser +type: other diff --git a/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml b/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml new file mode 100644 index 00000000000..6957d156161 --- /dev/null +++ b/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Persist source sha and target sha for merge pipelines +merge_request: 25417 +author: +type: added diff --git a/changelogs/unreleased/pl-serialize-ac-parameters.yml b/changelogs/unreleased/pl-serialize-ac-parameters.yml deleted file mode 100644 index aad222b5506..00000000000 --- a/changelogs/unreleased/pl-serialize-ac-parameters.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make `ActionController::Parameters` serializable for sidekiq jobs -merge_request: 24864 -author: -type: fixed diff --git a/changelogs/unreleased/profile-project-empty-state.yml b/changelogs/unreleased/profile-project-empty-state.yml deleted file mode 100644 index 484306d5b98..00000000000 --- a/changelogs/unreleased/profile-project-empty-state.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added empty project illustration and updated text to user profile overview -merge_request: 23973 -author: Fernando Arias -type: changed diff --git a/changelogs/unreleased/raise-on-unfiltered-params.yml b/changelogs/unreleased/raise-on-unfiltered-params.yml deleted file mode 100644 index 531e9ba807e..00000000000 --- a/changelogs/unreleased/raise-on-unfiltered-params.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Actually set raise_on_unfiltered_parameters to true -merge_request: 24443 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/rd-update-last_activity_on-on-logins-and-browsing-activity-54947.yml b/changelogs/unreleased/rd-update-last_activity_on-on-logins-and-browsing-activity-54947.yml deleted file mode 100644 index abce9dcc0c6..00000000000 --- a/changelogs/unreleased/rd-update-last_activity_on-on-logins-and-browsing-activity-54947.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update last_activity_on for Users on some main GET endpoints -merge_request: 24642 -author: -type: changed diff --git a/changelogs/unreleased/refactor-56366-extract-resolve-discussion-button.yml b/changelogs/unreleased/refactor-56366-extract-resolve-discussion-button.yml deleted file mode 100644 index 98859e8aa07..00000000000 --- a/changelogs/unreleased/refactor-56366-extract-resolve-discussion-button.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refactored NoteableDiscussion by extracting ResolveDiscussionButton -merge_request: 24505 -author: Martin Hobert -type: other diff --git a/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml b/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml new file mode 100644 index 00000000000..082075506c0 --- /dev/null +++ b/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml @@ -0,0 +1,5 @@ +--- +title: Extracted ResolveWithIssueButton to its own component +merge_request: 25093 +author: Martin Hobert +type: other diff --git a/changelogs/unreleased/refactor-56369-extract-jump-to-next-discussion-button.yml b/changelogs/unreleased/refactor-56369-extract-jump-to-next-discussion-button.yml deleted file mode 100644 index 9a0d16c2d70..00000000000 --- a/changelogs/unreleased/refactor-56369-extract-jump-to-next-discussion-button.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Extracted JumpToNextDiscussionButton to its own component -author: Martin Hobert -merge_request: 24506 -type: other diff --git a/changelogs/unreleased/refactor-56370-extract-reply-placeholder-component.yml b/changelogs/unreleased/refactor-56370-extract-reply-placeholder-component.yml deleted file mode 100644 index a216d294b30..00000000000 --- a/changelogs/unreleased/refactor-56370-extract-reply-placeholder-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Extracted ReplyPlaceholder to its own component -merge_request: 24507 -author: Martin Hobert -type: other diff --git a/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml b/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml deleted file mode 100644 index 06546bc5a8e..00000000000 --- a/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove Cancel all jobs button in general jobs list view -merge_request: -author: Jordi Llull -type: removed diff --git a/changelogs/unreleased/remove-diff-coloring.yml b/changelogs/unreleased/remove-diff-coloring.yml deleted file mode 100644 index 1ee1b525c35..00000000000 --- a/changelogs/unreleased/remove-diff-coloring.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'remove red/green colors from diff view of no-color syntax theme' -merge_request: 24582 -author: khm -type: changed diff --git a/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml b/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml deleted file mode 100644 index ce8e1829b48..00000000000 --- a/changelogs/unreleased/remove-gap-between-mr-tabs-and-file-header.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove extra space between MR tab bar and sticky file headers -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/search-title.yml b/changelogs/unreleased/search-title.yml deleted file mode 100644 index ff0933ed0b2..00000000000 --- a/changelogs/unreleased/search-title.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add 'in' filter that modifies scope of 'search' filter to issues and merge requests API -merge_request: 24350 -author: Hiroyuki Sato -type: added diff --git a/changelogs/unreleased/security-22076-sanitize-url-in-names.yml b/changelogs/unreleased/security-22076-sanitize-url-in-names.yml deleted file mode 100644 index 4e0ad4dd4c4..00000000000 --- a/changelogs/unreleased/security-22076-sanitize-url-in-names.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Sanitize user full name to clean up any URL to prevent mail clients from auto-linking - URLs -merge_request: 2793 -author: -type: security diff --git a/changelogs/unreleased/security-2770-verify-bundle-import-files.yml b/changelogs/unreleased/security-2770-verify-bundle-import-files.yml deleted file mode 100644 index dea40dd1ef1..00000000000 --- a/changelogs/unreleased/security-2770-verify-bundle-import-files.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Validate bundle files before unpacking them -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-55320-stored-xss-in-user-status.yml b/changelogs/unreleased/security-55320-stored-xss-in-user-status.yml deleted file mode 100644 index 8ea9ae0ccdf..00000000000 --- a/changelogs/unreleased/security-55320-stored-xss-in-user-status.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use sanitized user status message for user popover -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-stored-xss-via-katex.yml b/changelogs/unreleased/security-stored-xss-via-katex.yml deleted file mode 100644 index a71ae1123f2..00000000000 --- a/changelogs/unreleased/security-stored-xss-via-katex.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed XSS content in KaTex links -merge_request: -author: -type: security diff --git a/changelogs/unreleased/sh-disable-nil-user-id-identity-validation.yml b/changelogs/unreleased/sh-disable-nil-user-id-identity-validation.yml deleted file mode 100644 index 5af3bdce51b..00000000000 --- a/changelogs/unreleased/sh-disable-nil-user-id-identity-validation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix failed LDAP logins when nil user_id present -merge_request: 24749 -author: -type: fixed diff --git a/changelogs/unreleased/sh-encode-content-disposition.yml b/changelogs/unreleased/sh-encode-content-disposition.yml deleted file mode 100644 index b40ee6a85a8..00000000000 --- a/changelogs/unreleased/sh-encode-content-disposition.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Encode Content-Disposition filenames -merge_request: 24919 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml deleted file mode 100644 index d1d4412eb50..00000000000 --- a/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix duplicate project disk path in BackfillLegacyProjectRepositories -merge_request: 24213 -author: -type: changed diff --git a/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml b/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml deleted file mode 100644 index 87405fa0a78..00000000000 --- a/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Bitbucket Server importer error handling -merge_request: 24343 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-board-user-assigns.yml b/changelogs/unreleased/sh-fix-board-user-assigns.yml deleted file mode 100644 index 89c228107f0..00000000000 --- a/changelogs/unreleased/sh-fix-board-user-assigns.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix 403 errors when adding an assignee list in project boards -merge_request: 25263 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-cpp-templates-404.yml b/changelogs/unreleased/sh-fix-cpp-templates-404.yml new file mode 100644 index 00000000000..ac958d84099 --- /dev/null +++ b/changelogs/unreleased/sh-fix-cpp-templates-404.yml @@ -0,0 +1,5 @@ +--- +title: Fix 404s when C++ .gitignore template selected +merge_request: 25416 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml b/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml new file mode 100644 index 00000000000..e6c762f1d47 --- /dev/null +++ b/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Remove duplicate XHR request when requesting new pipeline page +merge_request: 25506 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-import-redirect-vulnerability.yml b/changelogs/unreleased/sh-fix-import-redirect-vulnerability.yml deleted file mode 100644 index addf327b69d..00000000000 --- a/changelogs/unreleased/sh-fix-import-redirect-vulnerability.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Alias GitHub and BitBucket OAuth2 callback URLs -merge_request: -author: -type: security diff --git a/changelogs/unreleased/sh-fix-issue-58103.yml b/changelogs/unreleased/sh-fix-issue-58103.yml new file mode 100644 index 00000000000..1599af23fed --- /dev/null +++ b/changelogs/unreleased/sh-fix-issue-58103.yml @@ -0,0 +1,5 @@ +--- +title: Properly handle multiple X-Forwarded-For addresses in runner IP +merge_request: 25511 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-pages-zip-constant.yml b/changelogs/unreleased/sh-fix-pages-zip-constant.yml deleted file mode 100644 index fcd8aa45825..00000000000 --- a/changelogs/unreleased/sh-fix-pages-zip-constant.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix uninitialized constant with GitLab Pages -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml b/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml deleted file mode 100644 index 414c8663049..00000000000 --- a/changelogs/unreleased/sh-fix-snippet-uploads-path-lookup.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix 404s with snippet uploads in object storage -merge_request: 24550 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml b/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml deleted file mode 100644 index 8bc1e4b4f8a..00000000000 --- a/changelogs/unreleased/sh-fix-upload-snippets-with-relative-url-root.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix 404s for snippet uploads when relative URL root used -merge_request: 24588 -author: -type: fixed diff --git a/changelogs/unreleased/sh-import-source-branch-github-forks.yml b/changelogs/unreleased/sh-import-source-branch-github-forks.yml deleted file mode 100644 index b5ea60202c0..00000000000 --- a/changelogs/unreleased/sh-import-source-branch-github-forks.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Create the source branch for a GitHub import -merge_request: 25064 -author: -type: fixed diff --git a/changelogs/unreleased/sh-issue-53419-fix.yml b/changelogs/unreleased/sh-issue-53419-fix.yml deleted file mode 100644 index ab8b65857e2..00000000000 --- a/changelogs/unreleased/sh-issue-53419-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Bitbucket Server import not allowing personal projects -merge_request: 23601 -author: -type: fixed diff --git a/changelogs/unreleased/sh-preload-associations-for-group-api.yml b/changelogs/unreleased/sh-preload-associations-for-group-api.yml deleted file mode 100644 index 24e424b7efb..00000000000 --- a/changelogs/unreleased/sh-preload-associations-for-group-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Eliminate N+1 queries in /api/groups/:id -merge_request: 24513 -author: -type: performance diff --git a/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml b/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml new file mode 100644 index 00000000000..f8ac345bc95 --- /dev/null +++ b/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml @@ -0,0 +1,5 @@ +--- +title: Remove N+1 query for tags in /admin/runners page +merge_request: 25572 +author: +type: performance diff --git a/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml b/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml new file mode 100644 index 00000000000..e7900e2230d --- /dev/null +++ b/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml @@ -0,0 +1,5 @@ +--- +title: Fix pagination and duplicate requests in environments page +merge_request: 25582 +author: +type: fixed diff --git a/changelogs/unreleased/shared_with_group_path.yml b/changelogs/unreleased/shared_with_group_path.yml deleted file mode 100644 index 73ba9a9f30a..00000000000 --- a/changelogs/unreleased/shared_with_group_path.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add group full path to project's shared_with_groups -merge_request: 24052 -author: Mathieu Parent -type: added diff --git a/changelogs/unreleased/support-chunking-in-client.yml b/changelogs/unreleased/support-chunking-in-client.yml deleted file mode 100644 index e50648ea4b2..00000000000 --- a/changelogs/unreleased/support-chunking-in-client.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix code search when text is larger than max gRPC message size -merge_request: 24111 -author: -type: changed diff --git a/changelogs/unreleased/syntax-highlighting-again.yml b/changelogs/unreleased/syntax-highlighting-again.yml deleted file mode 100644 index 14223fc0927..00000000000 --- a/changelogs/unreleased/syntax-highlighting-again.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix suggested changes syntax highlighting -merge_request: 25116 -author: -type: fixed diff --git a/changelogs/unreleased/test-permissions.yml b/changelogs/unreleased/test-permissions.yml deleted file mode 100644 index cfb69fdcb1e..00000000000 --- a/changelogs/unreleased/test-permissions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Disallows unauthorized users from accessing the pipelines section. -merge_request: -author: -type: security diff --git a/changelogs/unreleased/tooltips-to-top.yml b/changelogs/unreleased/tooltips-to-top.yml deleted file mode 100644 index 51bf127089e..00000000000 --- a/changelogs/unreleased/tooltips-to-top.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change spawning of tooltips to be top by default -merge_request: 21223 -author: -type: changed diff --git a/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml b/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml deleted file mode 100644 index 1af1fe09f33..00000000000 --- a/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'i18n: externalize strings from ''app/views/search''' -merge_request: 24297 -author: Tao Wang -type: other diff --git a/changelogs/unreleased/update-gitaly.yml b/changelogs/unreleased/update-gitaly.yml deleted file mode 100644 index 4ba42a689a7..00000000000 --- a/changelogs/unreleased/update-gitaly.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update Gitaly to v1.17.0 -merge_request: 24873 -author: -type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml deleted file mode 100644 index 7d92929221f..00000000000 --- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update GitLab Runner Helm Chart to 0.1.45 -merge_request: 24564 -author: -type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml new file mode 100644 index 00000000000..3bf55630c4d --- /dev/null +++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml @@ -0,0 +1,5 @@ +--- +title: Update GitLab Runner Helm Chart to 0.2.0 +merge_request: 25493 +author: +type: other diff --git a/changelogs/unreleased/update-gitlab-styles.yml b/changelogs/unreleased/update-gitlab-styles.yml deleted file mode 100644 index 379f0ad4486..00000000000 --- a/changelogs/unreleased/update-gitlab-styles.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update gitlab-styles to 2.5.1 -merge_request: 24336 -author: Jasper Maes -type: other diff --git a/changelogs/unreleased/update-pages-config-only-when-changed.yml b/changelogs/unreleased/update-pages-config-only-when-changed.yml deleted file mode 100644 index 8d9e02df678..00000000000 --- a/changelogs/unreleased/update-pages-config-only-when-changed.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Do not reload daemon if configuration file of pages does not change -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/update-pages-extensionless-urls.yml b/changelogs/unreleased/update-pages-extensionless-urls.yml deleted file mode 100644 index 13b3e1df500..00000000000 --- a/changelogs/unreleased/update-pages-extensionless-urls.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add support for extensionless pages URLs -merge_request: 24876 -author: -type: added diff --git a/changelogs/unreleased/update-sidekiq-cron.yml b/changelogs/unreleased/update-sidekiq-cron.yml deleted file mode 100644 index edce32e3753..00000000000 --- a/changelogs/unreleased/update-sidekiq-cron.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Update sidekiq-cron to 1.0.4 and use fugit to replace rufus-scheduler to parse - cron syntax -merge_request: 24235 -author: -type: other diff --git a/changelogs/unreleased/update-smooshpack.yml b/changelogs/unreleased/update-smooshpack.yml deleted file mode 100644 index a9222088d53..00000000000 --- a/changelogs/unreleased/update-smooshpack.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgraded Codesandbox smooshpack package -merge_request: -author: -type: other diff --git a/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml b/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml deleted file mode 100644 index 32259bfacd4..00000000000 --- a/changelogs/unreleased/update-spriteicon-from-icon-on-profile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update to GitLab SVG icon from Font Awesome in profile for location and work -merge_request: 24671 -author: Yoginth -type: changed diff --git a/changelogs/unreleased/update-ui-admin-appearance.yml b/changelogs/unreleased/update-ui-admin-appearance.yml deleted file mode 100644 index 7bc35029d77..00000000000 --- a/changelogs/unreleased/update-ui-admin-appearance.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update UI for admin appearance settings -merge_request: 24685 -author: -type: other diff --git a/changelogs/unreleased/update-workhorse-8-2-0.yml b/changelogs/unreleased/update-workhorse-8-2-0.yml deleted file mode 100644 index 7d593917a25..00000000000 --- a/changelogs/unreleased/update-workhorse-8-2-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update Workhorse to v8.2.0 -merge_request: 24909 -author: -type: fixed diff --git a/changelogs/unreleased/use-deployment-relation-to-fetch-environment-ce.yml b/changelogs/unreleased/use-deployment-relation-to-fetch-environment-ce.yml deleted file mode 100644 index 1ec276b4abc..00000000000 --- a/changelogs/unreleased/use-deployment-relation-to-fetch-environment-ce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use deployment relation to get an environment name -merge_request: 24890 -author: -type: performance diff --git a/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml b/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml deleted file mode 100644 index b41c3cfa1ab..00000000000 --- a/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added ability to upgrade cluster applications -merge_request: 24789 -author: -type: added diff --git a/changelogs/unreleased/web-ide-default-editor.yml b/changelogs/unreleased/web-ide-default-editor.yml new file mode 100644 index 00000000000..b98be5c16c2 --- /dev/null +++ b/changelogs/unreleased/web-ide-default-editor.yml @@ -0,0 +1,5 @@ +--- +title: Make the Web IDE the default editor +merge_request: +author: +type: added diff --git a/changelogs/unreleased/winh-add-list-dropdown-height.yml b/changelogs/unreleased/winh-add-list-dropdown-height.yml deleted file mode 100644 index 6bcedc15cc9..00000000000 --- a/changelogs/unreleased/winh-add-list-dropdown-height.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adjust height of "Add list" dropdown in issue boards -merge_request: 24227 -author: -type: fixed diff --git a/changelogs/unreleased/workhorse-8-3-0.yml b/changelogs/unreleased/workhorse-8-3-0.yml deleted file mode 100644 index 6ae01d64ae5..00000000000 --- a/changelogs/unreleased/workhorse-8-3-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update Workhorse to v8.3.0 -merge_request: 24959 -author: -type: other diff --git a/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml b/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml deleted file mode 100644 index 0ec76f9ce02..00000000000 --- a/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added Avatar in the settings sidebar -merge_request: 24515 -author: Yoginth -type: changed diff --git a/changelogs/unreleased/zj-feature-gate-set-project-path.yml b/changelogs/unreleased/zj-feature-gate-set-project-path.yml deleted file mode 100644 index b426a2f3fe7..00000000000 --- a/changelogs/unreleased/zj-feature-gate-set-project-path.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow setting of feature gates per project -merge_request: 24184 -author: -type: added diff --git a/changelogs/unreleased/zj-load-languages-from-database.yml b/changelogs/unreleased/zj-load-languages-from-database.yml new file mode 100644 index 00000000000..1688829b42c --- /dev/null +++ b/changelogs/unreleased/zj-load-languages-from-database.yml @@ -0,0 +1,5 @@ +--- +title: Load repository language from the database if detected before +merge_request: 25518 +author: +type: performance diff --git a/config/application.rb b/config/application.rb index 49e7f5836e4..1c11e347281 100644 --- a/config/application.rb +++ b/config/application.rb @@ -147,6 +147,8 @@ module Gitlab config.assets.precompile << "errors.css" config.assets.precompile << "csslab.css" + config.assets.precompile << "highlight/themes/*.css" + # Import gitlab-svgs directly from vendored directory config.assets.paths << "#{config.root}/node_modules/@gitlab/svgs/dist" config.assets.precompile << "icons.svg" diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb new file mode 100644 index 00000000000..1ed93019329 --- /dev/null +++ b/config/initializers/graphql.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +GraphQL::Field.accepts_definitions(authorize: GraphQL::Define.assign_metadata_key(:authorize)) +Types::BaseField.accepts_definition(:authorize) diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index be4183f39be..69a60a65e77 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -11,6 +11,10 @@ queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE # Default is to retry 25 times with exponential backoff. That's too much. Sidekiq.default_worker_options = { retry: 3 } +if Rails.env.development? + Sidekiq.default_worker_options[:backtrace] = true +end + enable_json_logs = Gitlab.config.sidekiq.log_format == 'json' Sidekiq.configure_server do |config| diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 1e094c03171..90cd787d5ac 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -86,3 +86,4 @@ - [delete_stored_files, 1] - [remote_mirror_notification, 2] - [import_issues_csv, 2] + - [chat_notification, 2] diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile index c20c8b77e6a..9be1ce2ff86 100644 --- a/danger/commit_messages/Dangerfile +++ b/danger/commit_messages/Dangerfile @@ -64,11 +64,12 @@ def too_many_changed_lines?(commit) lines_changed_in_commit(commit) >= 30 end -def lint_commits(commits) - failures = false - emoji_checker = EmojiChecker.new +def emoji_checker + @emoji_checker ||= EmojiChecker.new +end - unicode_emoji_regex = %r(( +def unicode_emoji_regex + @unicode_emoji_regex ||= %r(( [\u{1F300}-\u{1F5FF}] | [\u{1F1E6}-\u{1F1FF}] | [\u{2700}-\u{27BF}] | @@ -77,120 +78,132 @@ def lint_commits(commits) [\u{1F680}-\u{1F6FF}] | [\u{2600}-\u{26FF}] ))x +end + +def lint_commit(commit) + # For now we'll ignore merge commits, as getting rid of those is a problem + # separate from enforcing good commit messages. + return false if commit.message.start_with?('Merge branch') + + # We ignore revert commits as they are well structured by Git already + return false if commit.message.start_with?('Revert "') + + failures = false + subject, separator, details = commit.message.split("\n", 3) + + if subject.split.length < 3 + fail_commit( + commit, + 'The commit subject must contain at least three words' + ) + + failures = true + end + + if subject.length > 72 + fail_commit( + commit, + 'The commit subject may not be longer than 72 characters' + ) + + failures = true + elsif subject.length > 50 + warn_commit( + commit, + "This commit's subject line is acceptable, but please try to [reduce it to 50 characters](#{URL_LIMIT_SUBJECT})." + ) + end + + unless subject_starts_with_capital?(subject) + fail_commit(commit, 'The commit subject must start with a capital letter') + failures = true + end + + if subject.end_with?('.') + fail_commit(commit, 'The commit subject must not end with a period') + failures = true + end + + if separator && !separator.empty? + fail_commit( + commit, + 'The commit subject and body must be separated by a blank line' + ) + + failures = true + end + + details&.each_line do |line| + line = line.strip - commits.each do |commit| - # For now we'll ignore merge commits, as getting rid of those is a problem - # separate from enforcing good commit messages. - next if commit.message.start_with?('Merge branch') - - subject, separator, details = commit.message.split("\n", 3) - - if subject.split.length < 3 - fail_commit( - commit, - 'The commit subject must contain at least three words' - ) - - failures = true - end - - if subject.length > 72 - fail_commit( - commit, - 'The commit subject may not be longer than 72 characters' - ) - - failures = true - elsif subject.length > 50 - warn_commit( - commit, - "This commit's subject line is acceptable, but please try to [reduce it to 50 characters](#{URL_LIMIT_SUBJECT})." - ) - end - - unless subject_starts_with_capital?(subject) - fail_commit(commit, 'The commit subject must start with a capital letter') - failures = true - end - - if subject.end_with?('.') - fail_commit(commit, 'The commit subject must not end with a period') - failures = true - end - - if separator && !separator.empty? - fail_commit( - commit, - 'The commit subject and body must be separated by a blank line' - ) - - failures = true - end - - details&.each_line do |line| - line = line.strip - - next if line.length <= 72 - - url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length } - - # If the line includes a URL, we'll allow it to exceed 72 characters, but - # only if the line _without_ the URL does not exceed this limit. - next if line.length - url_size <= 72 - - fail_commit( - commit, - 'The commit body should not contain more than 72 characters per line' - ) - - failures = true - end - - if !details && too_many_changed_lines?(commit) - fail_commit( - commit, - 'Commits that change 30 or more lines across at least three files ' \ - 'must describe these changes in the commit body' - ) - - failures = true - end - - if emoji_checker.includes_emoji?(commit.message) - fail_commit( - commit, - 'Avoid the use of Markdown Emoji such as `:+1:`. ' \ - 'These add no value to the commit message, ' \ - 'and are displayed as plain text outside of GitLab' - ) - - failures = true - end - - if commit.message.match?(unicode_emoji_regex) - fail_commit( - commit, - 'Avoid the use of Unicode Emoji. ' \ - 'These add no value to the commit message, ' \ - 'and may not be displayed properly everywhere' - ) - - failures = true - end - - if commit.message.match?(%r(([\w\-\/]+)?(#|!|&|%)\d+\b)) - fail_commit( - commit, - 'Use full URLs instead of short references ' \ - '(`gitlab-org/gitlab-ce#123` or `!123`), as short references are ' \ - 'displayed as plain text outside of GitLab' - ) - - failures = true - end + next if line.length <= 72 + + url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length } + + # If the line includes a URL, we'll allow it to exceed 72 characters, but + # only if the line _without_ the URL does not exceed this limit. + next if line.length - url_size <= 72 + + fail_commit( + commit, + 'The commit body should not contain more than 72 characters per line' + ) + + failures = true + end + + if !details && too_many_changed_lines?(commit) + fail_commit( + commit, + 'Commits that change 30 or more lines across at least three files ' \ + 'must describe these changes in the commit body' + ) + + failures = true + end + + if emoji_checker.includes_emoji?(commit.message) + fail_commit( + commit, + 'Avoid the use of Markdown Emoji such as `:+1:`. ' \ + 'These add no value to the commit message, ' \ + 'and are displayed as plain text outside of GitLab' + ) + + failures = true + end + + if commit.message.match?(unicode_emoji_regex) + fail_commit( + commit, + 'Avoid the use of Unicode Emoji. ' \ + 'These add no value to the commit message, ' \ + 'and may not be displayed properly everywhere' + ) + + failures = true + end + + if commit.message.match?(%r(([\w\-\/]+)?(#|!|&|%)\d+\b)) + fail_commit( + commit, + 'Use full URLs instead of short references ' \ + '(`gitlab-org/gitlab-ce#123` or `!123`), as short references are ' \ + 'displayed as plain text outside of GitLab' + ) + + failures = true + end + + failures +end + +def lint_commits(commits) + failed = commits.select do |commit| + lint_commit(commit) end - if failures + if failed.any? markdown(<<~MARKDOWN) ## Commit message standards diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile index 2b09536f42a..0cf478b4f89 100644 --- a/danger/documentation/Dangerfile +++ b/danger/documentation/Dangerfile @@ -3,34 +3,34 @@ docs_paths_to_review = helper.changes_by_category[:docs] unless docs_paths_to_review.empty? - message 'This merge request adds or changes files that require a ' \ - 'review from the Docs team.' + message 'This merge request adds or changes files that require a review ' \ + 'from the Technical Writing team whether before or after merging.' markdown(<<~MARKDOWN) -## Docs review +## Documentation review -The following files require a review from the Documentation team: +The following files will require a review from the [Technical Writing](https://about.gitlab.com/handbook/product/technical-writing/) team: * #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")} -When your content is ready for review, assign the MR to a technical writer -according to the [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages) -in the table below. If necessary, mention them in a comment explaining what needs -to be reviewed. +Per the [documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html), the review may occur prior to merging this MR **or** after a maintainer has agreed to review and merge this MR without a tech writer review. (Meanwhile, you are welcome to involve a technical writer at any time if you have questions about writing or updating the documentation. GitLabbers are also welcome to use the [#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.) + +The technical writer who, by default, will review this content or collaborate as needed is dependent on the [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) to which the content applies: | Tech writer | Stage(s) | | ------------ | ------------------------------------------------------------ | | `@marcia` | ~Create ~Release + ~"development guidelines" | -| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitor ~Package ~Secure | +| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitor ~Package ~Secure | | `@eread` | ~Manage ~Configure ~Geo ~Verify | | `@mikelewis` | ~Plan | -You are welcome to mention them sooner if you have questions about writing or -updating the documentation. GitLabbers are also welcome to use the -[#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack. +**To request a review prior to merging** + +- Assign the MR to the applicable technical writer, above. If you are not sure which category the change falls within, or the change is not part of one of these categories, mention one of the usernames above. + +**To request a review to commence after merging** -If you are not sure which category the change falls within, or the change is not -part of one of these categories, mention one of the usernames above. +- Create an issue for a doc review [using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and assign it to the applicable technicial writer from the table. MARKDOWN unless gitlab.mr_labels.include?('Documentation') diff --git a/db/migrate/20180314145917_add_header_and_footer_banners_to_appearances_table.rb b/db/migrate/20180314145917_add_header_and_footer_banners_to_appearances_table.rb new file mode 100644 index 00000000000..8aba3448035 --- /dev/null +++ b/db/migrate/20180314145917_add_header_and_footer_banners_to_appearances_table.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddHeaderAndFooterBannersToAppearancesTable < ActiveRecord::Migration[4.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :appearances, :header_message, :text + add_column :appearances, :header_message_html, :text + + add_column :appearances, :footer_message, :text + add_column :appearances, :footer_message_html, :text + + add_column :appearances, :message_background_color, :text + add_column :appearances, :message_font_color, :text + end +end diff --git a/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb b/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb new file mode 100644 index 00000000000..45c7c0949c6 --- /dev/null +++ b/db/migrate/20190220150130_add_extra_shas_to_ci_pipelines.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddExtraShasToCiPipelines < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :ci_pipelines, :source_sha, :binary + add_column :ci_pipelines, :target_sha, :binary + end +end diff --git a/db/post_migrate/20181101091005_steal_digest_column.rb b/db/post_migrate/20181101091005_steal_digest_column.rb new file mode 100644 index 00000000000..58ea710c18a --- /dev/null +++ b/db/post_migrate/20181101091005_steal_digest_column.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class StealDigestColumn < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + Gitlab::BackgroundMigration.steal('DigestColumn') + end + + def down + # raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/post_migrate/20181101091124_remove_token_from_personal_access_tokens.rb b/db/post_migrate/20181101091124_remove_token_from_personal_access_tokens.rb new file mode 100644 index 00000000000..415373068d5 --- /dev/null +++ b/db/post_migrate/20181101091124_remove_token_from_personal_access_tokens.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class RemoveTokenFromPersonalAccessTokens < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + remove_column :personal_access_tokens, :token, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 25a645562ec..d835e48938d 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: 20190204115450) do +ActiveRecord::Schema.define(version: 20190220150130) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -37,6 +37,12 @@ ActiveRecord::Schema.define(version: 20190204115450) do t.integer "cached_markdown_version" t.text "new_project_guidelines" t.text "new_project_guidelines_html" + t.text "header_message" + t.text "header_message_html" + t.text "footer_message" + t.text "footer_message_html" + t.text "message_background_color" + t.text "message_font_color" t.string "favicon" end @@ -491,6 +497,8 @@ ActiveRecord::Schema.define(version: 20190204115450) do t.integer "failure_reason" t.integer "iid" t.integer "merge_request_id" + t.binary "source_sha" + t.binary "target_sha" t.index ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree t.index ["merge_request_id"], name: "index_ci_pipelines_on_merge_request_id", where: "(merge_request_id IS NOT NULL)", using: :btree t.index ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree @@ -1515,7 +1523,6 @@ ActiveRecord::Schema.define(version: 20190204115450) do create_table "personal_access_tokens", force: :cascade do |t| t.integer "user_id", null: false - t.string "token" t.string "name", null: false t.boolean "revoked", default: false t.date "expires_at" @@ -1524,7 +1531,6 @@ ActiveRecord::Schema.define(version: 20190204115450) do t.string "scopes", default: "--- []\n", null: false t.boolean "impersonation", default: false, null: false t.string "token_digest" - t.index ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree t.index ["token_digest"], name: "index_personal_access_tokens_on_token_digest", unique: true, using: :btree t.index ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree end diff --git a/doc/README.md b/doc/README.md index f87ff925e94..14e8d2edb90 100644 --- a/doc/README.md +++ b/doc/README.md @@ -284,6 +284,7 @@ The following documentation relates to the DevOps **Configure** stage: | [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. | | [Easy creation of Kubernetes<br/>clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) | Use Google Kubernetes Engine and GitLab. | | [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. | +| [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. | | [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. | | [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. | | [Protected variables](ci/variables/README.md#protected-variables) | Restrict variables to protected branches and tags. | diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md index 94a8803fff1..726622d8599 100644 --- a/doc/administration/auth/authentiq.md +++ b/doc/administration/auth/authentiq.md @@ -51,7 +51,7 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t ``` 1. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits. -See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers. + See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers. 1. Change `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` to the Client credentials you received in step 1. diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index 37e596f198f..d5d0d99ac24 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -451,7 +451,7 @@ ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt -b "$ba ### Invalid credentials when logging in - Make sure the user you are binding with has enough permissions to read the user's -tree and traverse it. + tree and traverse it. - Check that the `user_filter` is not blocking otherwise valid users. - Run the following check command to make sure that the LDAP settings are correct and GitLab can see your users: diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index e554c06532e..d95c3acec54 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -146,6 +146,14 @@ the share is exported and exists on the NFS server and try to remount. --- +## Upgrading GitLab HA + +GitLab HA installations can be upgraded with no downtime, but the +upgrade process must be carefully coordinated to avoid failures. See the +[Omnibus GitLab multi-node upgrade +document](https://docs.gitlab.com/omnibus/update/#multi-node--ha-deployment) +for more details. + Read more on high-availability configuration: 1. [Configure the database](database.md) diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md index bf5d064d79d..a52bc5c3b02 100644 --- a/doc/administration/high_availability/redis.md +++ b/doc/administration/high_availability/redis.md @@ -359,11 +359,17 @@ following section assumes you are using Omnibus GitLab Enterprise Edition. For the Omnibus Community Edition and installations from source, follow the [Redis HA source install](redis_source.md) guide. +NOTE: **Note:** If you are using an external Redis Sentinel instance, be sure +to exclude the `requirepass` parameter from the Sentinel +configuration. This parameter will cause clients to report `NOAUTH +Authentication required.`. [Redis Sentinel 3.2.x does not support +password authentication](https://github.com/antirez/redis/issues/3279). + Now that the Redis servers are all set up, let's configure the Sentinel servers. If you are not sure if your Redis servers are working and replicating -correctly, please read the [Troubleshooting Replication](#troubleshooting-replication) +correctly, please read the [Troubleshooting Replication](#troubleshooting-redis-replication) and fix it before proceeding with Sentinel setup. You must have at least `3` Redis Sentinel servers, and they need to diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md index 14e2784c419..be6b547372a 100644 --- a/doc/administration/high_availability/redis_source.md +++ b/doc/administration/high_availability/redis_source.md @@ -46,7 +46,7 @@ valuable information for the general setup. Assuming that the Redis master instance IP is `10.0.0.1`: -1. [Install Redis](../../install/installation.md#6-redis) +1. [Install Redis](../../install/installation.md#7-redis). 1. Edit `/etc/redis/redis.conf`: ```conf @@ -72,7 +72,7 @@ Assuming that the Redis master instance IP is `10.0.0.1`: Assuming that the Redis slave instance IP is `10.0.0.2`: -1. [Install Redis](../../install/installation.md#6-redis) +1. [Install Redis](../../install/installation.md#7-redis). 1. Edit `/etc/redis/redis.conf`: ```conf diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md index 05873e01a08..658b2f55d30 100644 --- a/doc/administration/incoming_email.md +++ b/doc/administration/incoming_email.md @@ -95,244 +95,249 @@ for a real-world example of this exploit. ### Omnibus package installations -1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the - feature and fill in the details for your specific IMAP server and email account: +1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature + and fill in the details for your specific IMAP server and email account (see [examples](#config-examples) below). - Configuration for Postfix mail server, assumes mailbox - incoming@gitlab.example.com +1. Reconfigure GitLab for the changes to take effect: - ```ruby - gitlab_rails['incoming_email_enabled'] = true + ```sh + sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart + ``` - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" +1. Verify that everything is configured correctly: - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "incoming" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" + ```sh + sudo gitlab-rake gitlab:incoming_email:check + ``` - # IMAP server host - gitlab_rails['incoming_email_host'] = "gitlab.example.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 143 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = false - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false +Reply by email should now be working. - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - # The IDLE command timeout. - gitlab_rails['incoming_email_idle_timeout'] = 60 +### Installations from source + +1. Go to the GitLab installation directory: + + ```sh + cd /home/git/gitlab ``` - Configuration for Gmail / Google Apps, assumes mailbox - gitlab-incoming@gmail.com +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature + and fill in the details for your specific IMAP server and email account (see [examples](#config-examples) below). - ```ruby - gitlab_rails['incoming_email_enabled'] = true +1. Enable `mail_room` in the init script at `/etc/default/gitlab`: + + ```sh + sudo mkdir -p /etc/default + echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab + ``` + +1. Restart GitLab: + + ```sh + sudo service gitlab restart + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production + ``` + +Reply by email should now be working. + +### Config examples + +#### Postfix + +Example configuration for Postfix mail server. Assumes mailbox incoming@gitlab.example.com. + +Example for Omnibus installs: + +```ruby +gitlab_rails['incoming_email_enabled'] = true + +# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. +# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). +gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" + +# Email account username +# With third party providers, this is usually the full email address. +# With self-hosted email servers, this is usually the user part of the email address. +gitlab_rails['incoming_email_email'] = "incoming" +# Email account password +gitlab_rails['incoming_email_password'] = "[REDACTED]" + +# IMAP server host +gitlab_rails['incoming_email_host'] = "gitlab.example.com" +# IMAP server port +gitlab_rails['incoming_email_port'] = 143 +# Whether the IMAP server uses SSL +gitlab_rails['incoming_email_ssl'] = false +# Whether the IMAP server uses StartTLS +gitlab_rails['incoming_email_start_tls'] = false + +# The mailbox where incoming mail will end up. Usually "inbox". +gitlab_rails['incoming_email_mailbox_name'] = "inbox" +# The IDLE command timeout. +gitlab_rails['incoming_email_idle_timeout'] = 60 +``` + +Example for source installs: + +```yaml +incoming_email: + enabled: true # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" + address: "incoming+%{key}@gitlab.example.com" # Email account username # With third party providers, this is usually the full email address. # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" + user: "incoming" # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" + password: "[REDACTED]" # IMAP server host - gitlab_rails['incoming_email_host'] = "imap.gmail.com" + host: "gitlab.example.com" # IMAP server port - gitlab_rails['incoming_email_port'] = 993 + port: 143 # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = true + ssl: false # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false + start_tls: false # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" + mailbox: "inbox" # The IDLE command timeout. - gitlab_rails['incoming_email_idle_timeout'] = 60 - ``` + idle_timeout: 60 +``` + +#### Gmail + +Example configuration for Gmail/G Suite. Assumes mailbox gitlab-incoming@gmail.com. + +Example for Omnibus installs: + +```ruby +gitlab_rails['incoming_email_enabled'] = true - Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes the - catch-all mailbox incoming@exchange.example.com +# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. +# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). +gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" - ```ruby - gitlab_rails['incoming_email_enabled'] = true +# Email account username +# With third party providers, this is usually the full email address. +# With self-hosted email servers, this is usually the user part of the email address. +gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" +# Email account password +gitlab_rails['incoming_email_password'] = "[REDACTED]" + +# IMAP server host +gitlab_rails['incoming_email_host'] = "imap.gmail.com" +# IMAP server port +gitlab_rails['incoming_email_port'] = 993 +# Whether the IMAP server uses SSL +gitlab_rails['incoming_email_ssl'] = true +# Whether the IMAP server uses StartTLS +gitlab_rails['incoming_email_start_tls'] = false + +# The mailbox where incoming mail will end up. Usually "inbox". +gitlab_rails['incoming_email_mailbox_name'] = "inbox" +# The IDLE command timeout. +gitlab_rails['incoming_email_idle_timeout'] = 60 +``` + +Example for source installs: + +```yaml +incoming_email: + enabled: true # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - # Exchange does not support sub-addressing, so a catch-all mailbox must be used. - gitlab_rails['incoming_email_address'] = "incoming-%{key}@exchange.example.com" + address: "gitlab-incoming+%{key}@gmail.com" # Email account username - # Typically this is the userPrincipalName (UPN) - gitlab_rails['incoming_email_email'] = "incoming@ad-domain.example.com" + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" + password: "[REDACTED]" # IMAP server host - gitlab_rails['incoming_email_host'] = "exchange.example.com" + host: "imap.gmail.com" # IMAP server port - gitlab_rails['incoming_email_port'] = 993 + port: 993 # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = true - ``` - -1. Reconfigure GitLab for the changes to take effect: + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false - ```sh - sudo gitlab-ctl reconfigure - sudo gitlab-ctl restart - ``` + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + # The IDLE command timeout. + idle_timeout: 60 +``` -1. Verify that everything is configured correctly: +#### MS Exchange - ```sh - sudo gitlab-rake gitlab:incoming_email:check - ``` +Example configuration for Microsoft Exchange mail server with IMAP enabled. Assumes the +catch-all mailbox incoming@exchange.example.com. -1. Reply by email should now be working. +Example for Omnibus installs: -### Installations from source +```ruby +gitlab_rails['incoming_email_enabled'] = true -1. Go to the GitLab installation directory: +# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. +# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). +# Exchange does not support sub-addressing, so a catch-all mailbox must be used. +gitlab_rails['incoming_email_address'] = "incoming-%{key}@exchange.example.com" - ```sh - cd /home/git/gitlab - ``` +# Email account username +# Typically this is the userPrincipalName (UPN) +gitlab_rails['incoming_email_email'] = "incoming@ad-domain.example.com" +# Email account password +gitlab_rails['incoming_email_password'] = "[REDACTED]" -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature - and fill in the details for your specific IMAP server and email account: +# IMAP server host +gitlab_rails['incoming_email_host'] = "exchange.example.com" +# IMAP server port +gitlab_rails['incoming_email_port'] = 993 +# Whether the IMAP server uses SSL +gitlab_rails['incoming_email_ssl'] = true +``` - ```sh - sudo editor config/gitlab.yml - ``` +Example for source installs: - Configuration for Postfix mail server, assumes mailbox - incoming@gitlab.example.com - - ```yaml - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "incoming" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "gitlab.example.com" - # IMAP server port - port: 143 - # Whether the IMAP server uses SSL - ssl: false - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - # The IDLE command timeout. - idle_timeout: 60 - ``` +```yaml +incoming_email: + enabled: true - Configuration for Gmail / Google Apps, assumes mailbox - gitlab-incoming@gmail.com - - ```yaml - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - # The IDLE command timeout. - idle_timeout: 60 - ``` - - Configuration for Microsoft Exchange mail server w/ IMAP enabled, assumes the - catch-all mailbox incoming@exchange.example.com - - ```yaml - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - # Exchange does not support sub-addressing, so a catch-all mailbox must be used. - address: "incoming-%{key}@exchange.example.com" - - # Email account username - # Typically this is the userPrincipalName (UPN) - user: "incoming@ad-domain.example.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "exchange.example.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - # The IDLE command timeout. - idle_timeout: 60 - ``` - -1. Enable `mail_room` in the init script at `/etc/default/gitlab`: - - ```sh - sudo mkdir -p /etc/default - echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab - ``` - -1. Restart GitLab: - - ```sh - sudo service gitlab restart - ``` + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + # Exchange does not support sub-addressing, so a catch-all mailbox must be used. + address: "incoming-%{key}@exchange.example.com" -1. Verify that everything is configured correctly: + # Email account username + # Typically this is the userPrincipalName (UPN) + user: "incoming@ad-domain.example.com" + # Email account password + password: "[REDACTED]" - ```sh - sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production - ``` + # IMAP server host + host: "exchange.example.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false -1. Reply by email should now be working. + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + # The IDLE command timeout. + idle_timeout: 60 +``` diff --git a/doc/administration/index.md b/doc/administration/index.md index ef692ea47ee..6e08d4633cd 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -51,8 +51,9 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Third party offers](../user/admin_area/settings/third_party_offers.md) - [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards. - [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages. -- [Merge request diffs](merge_request_diffs.md): Configure the diffs shown on merge requests +- [Merge request diffs storage](merge_request_diffs.md): Configure merge requests diffs external storage. - [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages to GitLab users through the UI. +- [Admin Area](../user/admin_area/index.md): for self-managed instance-wide configuration and maintenance. #### Customizing GitLab's appearance @@ -130,7 +131,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Job traces](job_traces.md): Information about the job traces (logs). - [Register Shared and specific Runners](../ci/runners/README.md#registering-a-shared-runner): Learn how to register and configure Shared and specific Runners to your own instance. - [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota): Limit the usage of pipeline minutes for Shared Runners. -- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enabling-auto-devops): Enable or disable Auto DevOps for your instance. +- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enablingdisabling-auto-devops): Enable or disable Auto DevOps for your instance. ## Git configuration options diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md index 94620c3d3a0..c34a9519ace 100644 --- a/doc/administration/merge_request_diffs.md +++ b/doc/administration/merge_request_diffs.md @@ -1,7 +1,6 @@ -# Merge request diffs administration +# Merge request diffs storage **[CORE ONLY]** -> **Notes:** -> - External merge request diffs introduced in GitLab 11.8 +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52568) in GitLab 11.8. Merge request diffs are size-limited copies of diffs associated with merge requests. When viewing a merge request, diffs are sourced from these copies @@ -16,9 +15,7 @@ large, in which case, switching to external storage is recommended. Merge request diffs can be stored on disk, or in object storage. In general, it is better to store the diffs in the database than on disk. -To enable external storage of merge request diffs: - ---- +To enable external storage of merge request diffs, follow the instructions below. **In Omnibus installations:** @@ -29,17 +26,15 @@ To enable external storage of merge request diffs: ``` 1. _The external diffs will be stored in in - `/var/opt/gitlab/gitlab-rails/shared/external-diffs`._ To change the path, - for example to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb` + `/var/opt/gitlab/gitlab-rails/shared/external-diffs`._ To change the path, + for example, to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb` and add the following line: ```ruby gitlab_rails['external_diffs_storage_path'] = "/mnt/storage/external-diffs" ``` -1. Save the file and [reconfigure GitLab][] for the changes to take effect. - ---- +1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. **In installations from source:** @@ -52,7 +47,7 @@ To enable external storage of merge request diffs: ``` 1. _The external diffs will be stored in - `/home/git/gitlab/shared/external-diffs`._ To change the path, for example + `/home/git/gitlab/shared/external-diffs`._ To change the path, for example, to `/mnt/storage/external-diffs`, edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: @@ -62,18 +57,18 @@ To enable external storage of merge request diffs: storage_path: /mnt/storage/external-diffs ``` -1. Save the file and [restart GitLab][] for the changes to take effect. +1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect. ### Using object storage -Instead of storing the external diffs on disk, we recommended you use an object +Instead of storing the external diffs on disk, we recommended the use of an object store like AWS S3 instead. This configuration relies on valid AWS credentials to be configured already. ### Object Storage Settings For source installations, these settings are nested under `external_diffs:` and -then `object_store:`. On omnibus installs, they are prefixed by +then `object_store:`. On Omnibus installations, they are prefixed by `external_diffs_object_store_`. | Setting | Description | Default | @@ -118,7 +113,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a } ``` - NOTE: if you are using AWS IAM profiles, be sure to omit the + Note that, if you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs. For example: ```ruby @@ -129,9 +124,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a } ``` -1. Save the file and [reconfigure GitLab][] for the changes to take effect. - ---- +1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. **In installations from source:** @@ -151,4 +144,4 @@ The connection settings match those provided by [Fog](https://github.com/fog), a region: eu-central-1 ``` -1. Save the file and [restart GitLab][] for the changes to take effect. +1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect. diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md index 1f431f8bd62..ab43ec2cc4f 100644 --- a/doc/administration/monitoring/performance/grafana_configuration.md +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -34,7 +34,7 @@ Test Connection to ensure the configuration is correct. - **Default**: Checked - **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) - **Url**: `https://localhost:8086` (Or the remote URL if you've installed InfluxDB -on a separate server) + on a separate server) - **Access**: proxy - **Database**: gitlab - **User**: admin (Or the username configured when setting up InfluxDB) diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md index 6a55dbe1eb4..95f497a1470 100644 --- a/doc/administration/monitoring/performance/performance_bar.md +++ b/doc/administration/monitoring/performance/performance_bar.md @@ -10,11 +10,11 @@ It allows you to see (from left to right): - the current host serving the page - the timing of the page (backend, frontend) - time taken and number of DB queries, click through for details of these queries -![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png) + ![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png) - time taken and number of [Gitaly] calls, click through for details of these calls -![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png) + ![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png) - profile of the code used to generate the page, line by line. In the profile view, the numbers in the left panel represent wall time, cpu time, and number of calls (based on [rblineprof](https://github.com/tmm1/rblineprof)). -![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png) + ![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png) - time taken and number of calls to Redis - time taken and number of background jobs created by Sidekiq - time taken and number of Ruby GC calls diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 6ea0ac0d495..3bfcc9a289e 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -17,10 +17,7 @@ GitLab monitors its own internal service metrics, and makes them available at th `/-/metrics` endpoint. Unlike other [Prometheus] exporters, in order to access it, the client IP needs to be [included in a whitelist][whitelist]. -Currently the embedded Prometheus server is not automatically configured to -collect metrics from this endpoint. We recommend setting up another Prometheus -server, because the embedded server configuration is overwritten once every -[reconfigure of GitLab][reconfigure]. In the future this will not be required. +For Omnibus and Chart installations, these metrics are automatically enabled and collected as of [GitLab 9.4](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1702). For source installations or earlier versions, these metrics will need to be enabled manually and collected by a Prometheus server. ## Unicorn Metrics available diff --git a/doc/administration/operations/index.md b/doc/administration/operations/index.md index a16fc7ae74f..32f36d68c50 100644 --- a/doc/administration/operations/index.md +++ b/doc/administration/operations/index.md @@ -3,20 +3,20 @@ Keep your GitLab instance up and running smoothly. - [Clean up Redis sessions](cleaning_up_redis_sessions.md): Prior to GitLab 7.3, -user sessions did not automatically expire from Redis. If -you have been running a large GitLab server (thousands of users) since before -GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis -database after you upgrade to GitLab 7.3. + user sessions did not automatically expire from Redis. If + you have been running a large GitLab server (thousands of users) since before + GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis + database after you upgrade to GitLab 7.3. - [Moving repositories](moving_repositories.md): Moving all repositories managed -by GitLab to another file system or another server. + by GitLab to another file system or another server. - [Sidekiq MemoryKiller](sidekiq_memory_killer.md): Configure Sidekiq MemoryKiller -to restart Sidekiq. + to restart Sidekiq. - [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer. - Speed up SSH operations by [Authorizing SSH users via a fast, -indexed lookup to the GitLab database](fast_ssh_key_lookup.md), and/or -by [doing away with user SSH keys stored on GitLab entirely in favor -of SSH certificates](ssh_certificates.md). + indexed lookup to the GitLab database](fast_ssh_key_lookup.md), and/or + by [doing away with user SSH keys stored on GitLab entirely in favor + of SSH certificates](ssh_certificates.md). - [Filesystem Performance Benchmarking](filesystem_benchmarking.md): Filesystem -performance can have a big impact on GitLab performance, especially for actions -that read or write Git repositories. This information will help benchmark -filesystem performance against known good and bad real-world systems. + performance can have a big impact on GitLab performance, especially for actions + that read or write Git repositories. This information will help benchmark + filesystem performance against known good and bad real-world systems. diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 5c809f25fbd..279ad018aed 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -337,10 +337,10 @@ The default is 100MB. You may want to run GitLab Pages daemon on a separate server in order to decrease the load on your main application server. Follow the steps below to configure GitLab Pages in a separate server. -1. Suppose you have the main GitLab application server named `app1`. Prepare -new Linux server (let's call it `app2`), create NFS share there and configure access to -this share from `app1`. Let's use the default GitLab Pages folder `/var/opt/gitlab/gitlab-rails/shared/pages` -as the shared folder on `app2` and mount it to `/mnt/pages` on `app1`. +1. Suppose you have the main GitLab application server named `app1`. Prepare + new Linux server (let's call it `app2`), create NFS share there and configure access to + this share from `app1`. Let's use the default GitLab Pages folder `/var/opt/gitlab/gitlab-rails/shared/pages` + as the shared folder on `app2` and mount it to `/mnt/pages` on `app1`. 1. On `app2` install GitLab omnibus and modify `/etc/gitlab/gitlab.rb` this way: @@ -365,7 +365,7 @@ as the shared folder on `app2` and mount it to `/mnt/pages` on `app1`. pages_external_url "http://<your-pages-domain>" gitlab_rails['pages_path'] = "/mnt/pages" ``` - + 1. Run `sudo gitlab-ctl reconfigure`. ## Backup diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md index 9f2b4d9075a..60800d445b8 100644 --- a/doc/administration/pages/source.md +++ b/doc/administration/pages/source.md @@ -88,12 +88,13 @@ since that is needed in all configurations. ### Wildcard domains ->**Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> -> --- -> -> URL scheme: `http://page.example.io` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) + +--- + +URL scheme: `http://page.example.io` This is the minimum setup that you can use Pages with. It is the base for all other setups as described below. Nginx will proxy all requests to the daemon. diff --git a/doc/administration/polling.md b/doc/administration/polling.md index 35aaa20df2c..a1077614677 100644 --- a/doc/administration/polling.md +++ b/doc/administration/polling.md @@ -10,15 +10,15 @@ say that issue notes poll every 2 seconds, and issue titles poll every 5 seconds; these are _not_ the actual values. - 1 is the default, and recommended for most installations. (Issue notes poll -every 2 seconds, and issue titles poll every 5 seconds.) + every 2 seconds, and issue titles poll every 5 seconds.) - 0 will disable UI polling completely. (On the next poll, clients will stop -polling for updates.) + polling for updates.) - A value greater than 1 will slow polling down. If you see issues with -database load from lots of clients polling for updates, increasing the -multiplier from 1 can be a good compromise, rather than disabling polling -completely. (For example: If this is set to 2, then issue notes poll every 4 -seconds, and issue titles poll every 10 seconds.) + database load from lots of clients polling for updates, increasing the + multiplier from 1 can be a good compromise, rather than disabling polling + completely. (For example: If this is set to 2, then issue notes poll every 4 + seconds, and issue titles poll every 10 seconds.) - A value between 0 and 1 will make the UI poll more frequently (so updates -will show in other sessions faster), but is **not recommended**. 1 should be -fast enough. (For example, if this is set to 0.5, then issue notes poll every -1 second, and issue titles poll every 2.5 seconds.) + will show in other sessions faster), but is **not recommended**. 1 should be + fast enough. (For example, if this is set to 0.5, then issue notes poll every + 1 second, and issue titles poll every 2.5 seconds.) diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index 0d863594fc7..b295b7d5dc4 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -60,6 +60,7 @@ Runs the following rake tasks: It will check that each component was set up according to the installation guide and suggest fixes for issues found. You may also have a look at our Troubleshooting Guides: + - [Troubleshooting Guide (GitLab)](http://docs.gitlab.com/ee/README.html#troubleshooting) - [Troubleshooting Guide (Omnibus Gitlab)](http://docs.gitlab.com/omnibus/README.html#troubleshooting) diff --git a/doc/api/README.md b/doc/api/README.md index 89069fe60e1..48d9ad8f30d 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -134,7 +134,7 @@ Endpoints are available for: ## Road to GraphQL Going forward, we will start on moving to -[GraphQL](http://graphql.org/learn/best-practices/) and deprecate the use of +[GraphQL](graphql/index.md) and deprecate the use of controller-specific endpoints. GraphQL has a number of benefits: 1. We avoid having to maintain two different APIs. diff --git a/doc/api/boards.md b/doc/api/boards.md index 2a2622736c3..28c73db6b98 100644 --- a/doc/api/boards.md +++ b/doc/api/boards.md @@ -37,7 +37,7 @@ Example response: "web_url": "http://example.com/diaspora/diaspora-project-site" }, "milestone": { - "id": 12 + "id": 12, "title": "10.0" }, "lists" : [ @@ -95,7 +95,7 @@ Example response: ```json { "id": 1, - "name:": "project issue board", + "name": "project issue board", "project": { "id": 5, "name": "Diaspora Project Site", @@ -106,7 +106,7 @@ Example response: "web_url": "http://example.com/diaspora/diaspora-project-site" }, "milestone": { - "id": 12 + "id": 12, "title": "10.0" }, "lists" : [ diff --git a/doc/api/issues.md b/doc/api/issues.md index 0571f280d2a..cb5789e76b7 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -32,6 +32,7 @@ GET /issues?author_id=5 GET /issues?assignee_id=5 GET /issues?my_reaction_emoji=star GET /issues?search=foo&in=title +GET /issues?confidential=true ``` | Attribute | Type | Required | Description | @@ -52,6 +53,7 @@ GET /issues?search=foo&in=title | `created_before` | datetime | no | Return issues created on or before the given time | | `updated_after` | datetime | no | Return issues updated on or after the given time | | `updated_before` | datetime | no | Return issues updated on or before the given time | +| `confidential ` | Boolean | no | Filter confidential or public issues. | ```bash curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues @@ -148,6 +150,7 @@ GET /groups/:id/issues?search=issue+title+or+description GET /groups/:id/issues?author_id=5 GET /groups/:id/issues?assignee_id=5 GET /groups/:id/issues?my_reaction_emoji=star +GET /groups/:id/issues?confidential=true ``` | Attribute | Type | Required | Description | @@ -168,6 +171,7 @@ GET /groups/:id/issues?my_reaction_emoji=star | `created_before` | datetime | no | Return issues created on or before the given time | | `updated_after` | datetime | no | Return issues updated on or after the given time | | `updated_before` | datetime | no | Return issues updated on or before the given time | +| `confidential ` | Boolean | no | Filter confidential or public issues. | ```bash curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues @@ -264,6 +268,7 @@ GET /projects/:id/issues?search=issue+title+or+description GET /projects/:id/issues?author_id=5 GET /projects/:id/issues?assignee_id=5 GET /projects/:id/issues?my_reaction_emoji=star +GET /projects/:id/issues?confidential=true ``` | Attribute | Type | Required | Description | @@ -284,6 +289,8 @@ GET /projects/:id/issues?my_reaction_emoji=star | `created_before` | datetime | no | Return issues created on or before the given time | | `updated_after` | datetime | no | Return issues updated on or after the given time | | `updated_before` | datetime | no | Return issues updated on or before the given time | +| `confidential ` | Boolean | no | Filter confidential or public issues. | + ```bash curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues diff --git a/doc/api/milestones.md b/doc/api/milestones.md index fa8f8a0bcf0..897184d51af 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -130,3 +130,18 @@ Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user - `milestone_id` (required) - The ID of a project milestone + +## Promote project milestone to a group milestone + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53861) in GitLab 11.9 + +Only for users with developer access to the group. + +``` +POST /projects/:id/milestones/:milestone_id/promote +``` + +Parameters: + +- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +- `milestone_id` (required) - The ID of a project milestone diff --git a/doc/api/projects.md b/doc/api/projects.md index 3c0c956ddc2..0a950352ecf 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -771,6 +771,8 @@ POST /projects/:id/fork | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `namespace` | integer/string | yes | The ID or path of the namespace that the project will be forked to | +| `path` | string | no | The path that will be assigned to the resultant project after forking | +| `name` | string | no | The name that will be assigned to the resultant project after forking | ## List Forks of a project diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 8c1d982f394..6fcc06ea8cd 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -171,6 +171,7 @@ Parameters: If the commit fails for any reason we return a 400 error with a non-specific error message. Possible causes for a failed commit include: + - the `file_path` contained `/../` (attempted directory traversal); - the new file contents were identical to the current file contents, i.e. the user tried to make an empty commit; diff --git a/doc/api/runners.md b/doc/api/runners.md index 4aa0e4543e5..35c18649fec 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -478,7 +478,7 @@ Example response: ## Delete a registered Runner -Deletes a registed Runner. +Deletes a registered Runner. ``` DELETE /runners diff --git a/doc/api/services.md b/doc/api/services.md index 5d5aa3e5b3e..c44f5cc5781 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -522,6 +522,7 @@ Parameters: | `project_key` | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. | | `username` | string | yes | The username of the user created to be used with GitLab/JIRA. | | `password` | string | yes | The password of the user created to be used with GitLab/JIRA. | +| `active` | boolean | no | Activates or deactivates the service. Defaults to false (deactivated). | | `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. | ### Delete JIRA service @@ -1101,3 +1102,39 @@ GET /projects/:id/services/mock-ci ``` [11435]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11435 + +## YouTrack + +YouTrack issue tracker + +### Create/Edit YouTrack service + +Set YouTrack service for a project. + +``` +PUT /projects/:id/services/youtrack +``` + +Parameters: + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `issues_url` | string | true | Issue url | +| `project_url` | string | true | Project url | +| `description` | string | false | Description | + +### Delete YouTrack Service + +Delete YouTrack service for a project. + +``` +DELETE /projects/:id/services/youtrack +``` + +### Get YouTrack Service Settings + +Get YouTrack service settings for a project. + +``` +GET /projects/:id/services/youtrack +``` diff --git a/doc/ci/README.md b/doc/ci/README.md index 4e066a0df97..4c3c9920b93 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -5,131 +5,194 @@ description: "Learn how to use GitLab CI/CD, the GitLab built-in Continuous Inte # GitLab Continuous Integration (GitLab CI/CD) -![Pipeline graph](img/cicd_pipeline_infograph.png) +GitLab provides tools for continuously integrating and delivering code. + +Within the [entire DevOps lifecycle](../README.md#the-entire-devops-lifecycle), GitLab CI/CD spans +the [Verify (CI)](../README.md#verify) and [Release (CD)](../README.md#release) stages. -The benefits of Continuous Integration are huge when automation plays an -integral part of your workflow. GitLab comes with built-in Continuous -Integration, Continuous Deployment, and Continuous Delivery support -to build, test, and deploy your application. +## Overview -Here's some info we've gathered to get you started. +CI/CD is a vast area, so GitLab provides documentation for all levels of expertise. Consult the following table to find the right documentation for you: -## Getting started +| Level of expertise | Resource | +|:------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------| +| New to the concepts of CI and CD | For a high-level overview, see the [GitLab Continuous Integration & Delivery](https://about.gitlab.com/product/continuous-integration/) product page. | +| Familiar with the purpose of CI/CD | Delve into GitLab CI/CD by continuing down the page, starting with our [introduction](#introduction). | +| Familiar with GitLab CI/CD concepts | After getting familiar with GitLab CI/CD, let us walk you through a simple example in our [quick start guide](quick_start/README.md). | +| A GitLab CI/CD expert | Jump straight to our [`.gitlab.yml`](yaml/README.md) reference. | -The first steps towards your GitLab CI/CD journey. +## Introduction -- [Getting started with GitLab CI/CD](quick_start/README.md): understand how GitLab CI/CD works. -- [GitLab CI/CD configuration file: `.gitlab-ci.yml`](yaml/README.md) - Learn all about the ins and outs of `.gitlab-ci.yml`. -- [Pipelines and jobs](pipelines.md): configure your GitLab CI/CD pipelines to build, test, and deploy your application. -- Runners: The [GitLab Runner](https://docs.gitlab.com/runner/) is responsible by running the jobs in your CI/CD pipeline. On GitLab.com, Shared Runners are enabled by default, so -you don't need to set up anything to start to use them with GitLab CI/CD. +The following introduces the process of continuous integration (CI) and continuous delivery (CD): -### Introduction to GitLab CI/CD +![Pipeline graph](img/cicd_pipeline_infograph.png) -- Article (2016-08-05): [Continuous Integration, Delivery, and Deployment with GitLab - Intro to CI/CD](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/) -- Article (2015-12-14): [Getting started with GitLab and GitLab CI - Intro to CI](https://about.gitlab.com/2015/12/14/getting-started-with-gitlab-and-gitlab-ci/) -- Article (2017-07-13): [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) -- Article (2017-05-22): [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/) -- **Videos:** - - Demo (Streamed live on Jul 17, 2017): [GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195) - - Demo (March, 2017): [How to get started using CI/CD with GitLab](https://about.gitlab.com/2017/03/13/ci-cd-demo/) - - Webcast (April, 2016): [Getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/) -- **Third-party videos:** - - [Intégration continue avec GitLab (September, 2016)](https://www.youtube.com/watch?v=URcMBXjIr24&t=13s) - - [GitLab CI for Minecraft Plugins (July, 2016)](https://www.youtube.com/watch?v=Z4pcI9F8yf8) +In this illustration: -### Why GitLab CI/CD? +- New code is combined with existing code through a commit to a project's [repository](../user/project/repository/index.md). +- The newly combined code is sent to a CI [pipeline](pipelines.md) where: + - The code is [built](../user/project/pipelines/job_artifacts.md). + - Unit and integration tests are run over the built code. +- Assuming the build and tests are successful, a CD pipeline is triggered to allow for: + - Review using [Review Apps](review_apps/index.md). + - Deploying to configured [environments](environments.md). - - Article (2016-10-17): [Why We Chose GitLab CI for our CI/CD Solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/) - - Article (2016-07-22): [Building our web-app on GitLab CI: 5 reasons why Captain Train migrated from Jenkins to GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/) +The benefits of CI/CD are vast, allowing automation to be an integral part of your workflow for testing, building, deploying, and monitoring your code. -## Exploring GitLab CI/CD +Because CI and CD with GitLab is broad topic with many possibilities, the rest of this section provides +links to topics and resources needed to make use of GitLab CI/CD. -- [CI/CD Variables](variables/README.md) - Learn how to use variables defined in - your `.gitlab-ci.yml` or the ones defined in your project's settings - - [Where variables can be used](variables/where_variables_can_be_used.md) - A - deeper look on where and how the CI/CD variables can be used -- **The permissions model** - Learn about the access levels a user can have for - performing certain CI actions - - [User permissions](../user/permissions.md#gitlab-ci) - - [Job permissions](../user/permissions.md#job-permissions) -- [Configure a Runner, the application that runs your jobs](runners/README.md) -- Article (2016-03-01): [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/) -- Article (2016-07-29): [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/) -- Article (2016-08-26): [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) -- Article (2016-05-23): [Introduction to GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/) +## Essentials -## Advanced use +The following documentation provides the minimum required knowledge for making use of GitLab CI/CD: -Once you get familiar with the basics of GitLab CI/CD, it's time to dive in and -learn how to leverage its potential even more. +| Topic | Description | +|:------------------------------------------------------------------------|:---------------------------------------------------------| +| [Getting started with GitLab CI/CD](quick_start/README.md) | Outlines the first steps for configuring GitLab CI/CD. | +| [Introduction to pipelines and jobs](pipelines.md) | Provides an overview of GitLab CI/CD and jobs. | +| [Configuration of your pipelines with `.gitlab-ci.yml`](yaml/README.md) | A comprehensive reference for the `.gitlab-ci.yml` file. | -- [Environments and deployments](environments.md): Separate your jobs into - environments and use them for different purposes like testing, building and - deploying -- [Job artifacts](../user/project/pipelines/job_artifacts.md) -- [Caching dependencies](caching/index.md) -- [Git submodules](git_submodules.md) - How to run your CI jobs when Git - submodules are involved -- [Pipelines for merge requests](merge_request_pipelines/index.md) -- [Use SSH keys in your build environment](ssh_keys/README.md) -- [Trigger pipelines through the GitLab API](triggers/README.md) -- [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md) -- [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or - more Kubernetes clusters to your project -- [Interactive web terminal](interactive_web_terminal/index.md) - Open an interactive - web terminal to debug the running jobs +NOTE: **Note:** +Familiarity with [GitLab Runner](https://docs.gitlab.com/runner/) is useful because it is +responsible for running the jobs in your CI/CD pipeline. On GitLab.com, shared Runners are enabled +by default so you don't need to set up anything to get started. -## GitLab CI/CD for Docker +### Auto DevOps -Leverage the power of Docker to run your CI pipelines. +An alternative to manually configuring CI/CD, GitLab supports [Auto DevOps](../topics/autodevops/index.md), +which: -- [Use Docker images with GitLab Runner](docker/using_docker_images.md) -- [Use CI to build Docker images](docker/using_docker_build.md) -- [CI services (linked Docker containers)](services/README.md) -- Article (2016-03-01): [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/) +- Provides simplified setup and execution of CI/CD. +- Allows GitLab to automatically detect, build, test, deploy, and monitor your applications. -## Review Apps +## Basic usage -- [Review Apps documentation](review_apps/index.md) -- Article (2016-11-22): [Introducing Review Apps](https://about.gitlab.com/2016/11/22/introducing-review-apps/) -- [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/) +With basic knowledge of how GitLab CI/CD works, the following documentation extends your knowledge +into more features: -## Auto DevOps +| Topic | Description | +|:-------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------| +| [CI/CD Variables](variables/README.md) | How environment variables can be configured and made available in pipelines. | +| [Where variables can be used](variables/where_variables_can_be_used.md) | A deeper look into where and how CI/CD variables can be used. | +| [User](../user/permissions.md#gitlab-ci) and [job](../user/permissions.md#job-permissions) permissions | Learn about the access levels a user can have for performing certain CI actions. | +| [Configuring GitLab Runners](runners/README.md) | Documentation for configuring [GitLab Runner](https://docs.gitlab.com/runner/). | -- [Auto DevOps](../topics/autodevops/index.md): Auto DevOps automatically detects, builds, tests, deploys, and monitors your applications. +## Advanced usage -## GitLab CI for GitLab Pages +Once you get familiar with the basics of GitLab CI/CD, consult the following documentation to make +use of advanced features: -See the documentation on [GitLab Pages](../user/project/pages/index.md). +| Topic | Description | +|:---------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------| +| [Introduction to environments and deployments](environments.md) | Learn how to separate your jobs into environments and use them for different purposes like testing, building and, deploying. | +| [Job artifacts](../user/project/pipelines/job_artifacts.md) | Learn about the output of jobs. | +| [Cache dependencies in GitLab CI/CD](caching/index.md) | Discover how to speed up pipelines using caching. | +| [Using Git submodules with GitLab CI](git_submodules.md) | How to run your CI jobs when using Git submodules. | +| [Pipelines for merge requests](merge_request_pipelines/index.md) | Create pipelines specifically for merge requests. | +| [Using SSH keys with GitLab CI/CD](ssh_keys/README.md) | Use SSH keys in your build environment. | +| [Triggering pipelines through the API](triggers/README.md) | Use the GitLab API to trigger a pipeline. | +| [Pipeline schedules](../user/project/pipelines/schedules.md) | Trigger pipelines on a schedule. | +| [Connecting GitLab with a Kubernetes cluster](../user/project/clusters/index.md) | Integrate one or more Kubernetes clusters to your project. | +| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. | +| [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. | + +### GitLab Pages + +GitLab CI/CD can be used to build and host static websites. For more information, see the +documentation on [GitLab Pages](../user/project/pages/index.md). ## Examples -Check the [GitLab CI/CD examples](examples/README.md) for a collection of tutorials and guides on setting up your CI/CD pipeline for various programming languages, frameworks, -and operating systems. +Check out the [GitLab CI/CD examples](examples/README.md) for a collection of tutorials and guides on +setting up your CI/CD pipeline for various programming languages, frameworks, and operating systems. + +## Administration + +As a GitLab administrator, you can change the default behavior of GitLab CI/CD for: + +- An [entire GitLab instance](../user/admin_area/settings/continuous_integration.md). +- Specific projects, using [pipelines settings](../user/project/pipelines/settings.md). + +See also: + +- [How to enable or disable GitLab CI/CD](enable_or_disable_ci.md). +- Other [CI administration settings](../administration/index.md#continuous-integration-settings). + +## Using Docker + +Docker is commonly used with GitLab CI/CD. Learn more about how to to accomplish this with the following +documentation: + +| Topic | Description | +|:-------------------------------------------------------------------------|:-------------------------------------------------------------------------| +| [Using Docker images](docker/using_docker_images.md) | Use GitLab and GitLab Runner with Docker to build and test applications. | +| [Building Docker images with GitLab CI/CD](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. | + +Related topics include: -## Integrations +- [Docker integration](docker/README.md). +- [CI services (linked Docker containers)](services/README.md). +- [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/) (article). -- Article (2016-06-09): [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) -- Article (2016-05-05): [Getting Started with GitLab and Shippable Continuous Integration](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/) -- Article (2016-04-19): [GitLab Partners with DigitalOcean to make Continuous Integration faster, safer, and more affordable](https://about.gitlab.com/2016/04/19/gitlab-partners-with-digitalocean-to-make-continuous-integration-faster-safer-and-more-affordable/) +## Further resources -## Special configuration (GitLab admin) +This section provides further resources to help you get familiar with GitLab CI/CD. -As a GitLab administrator, you can change the default behavior of GitLab CI/CD in -your whole GitLab instance as well as in each project. +### Articles -- [Continuous Integration admin settings](../administration/index.md#continuous-integration-settings) -- **Project specific:** - - [Pipelines settings](../user/project/pipelines/settings.md) - - [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md) -- **Affecting the whole GitLab instance:** - - [Continuous Integration admin settings](../user/admin_area/settings/continuous_integration.md) +The following table provides a list of articles about CI/CD, sorted in reverse chronological order of publish date: + +| Publish Date | Article | +|:-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 2017-07-13 | [Making CI easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/). | +| 2017-05-22 | [Fast and natural continuous integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/). | +| 2016-11-22 | [Introducing Review Apps](https://about.gitlab.com/2016/11/22/introducing-review-apps/). | +| 2016-08-26 | [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/). | +| 2016-08-05 | [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/). | +| 2016-07-29 | [GitLab CI: Run jobs sequentially, in parallel or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/). | +| 2016-06-09 | [Continuous Delivery with GitLab and Convox](https://about.gitlab.com/2016/06/09/continuous-delivery-with-gitlab-and-convox/) | +| 2016-05-23 | [GitLab Container Registry](https://about.gitlab.com/2016/05/23/gitlab-container-registry/). | +| 2016-05-05 | [Getting Started with GitLab and Shippable Continuous Integration](https://about.gitlab.com/2016/05/05/getting-started-gitlab-and-shippable/) | +| 2016-04-19 | [GitLab Partners with DigitalOcean to make Continuous Integration faster, safer, and more affordable](https://about.gitlab.com/2016/04/19/gitlab-partners-with-digitalocean-to-make-continuous-integration-faster-safer-and-more-affordable/) | +| 2015-03-01 | [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/). | +| 2015-12-14 | [Getting started with GitLab and GitLab CI](https://about.gitlab.com/2015/12/14/getting-started-with-gitlab-and-gitlab-ci/). | + +### Videos + +The following table provides a list of videos about CI/CD, sorted in reverse chronological order of publish date: + +| Publish Date | Video | +|:-------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 2017-07-17 | [GitLab CI/CD Deep Dive](https://youtu.be/pBe4t1CD8Fc?t=195). | +| 2017-03-13 | [Demo: CI/CD with GitLab in action](https://about.gitlab.com/2017/03/13/ci-cd-demo/). | +| 2016-04-20 | [Webcast Recording and Slides: Getting started with CI in GitLab](https://about.gitlab.com/2016/04/20/webcast-recording-and-slides-introduction-to-ci-in-gitlab/). | + +In addition, the following third-party videos are available: + +- [Intégration continue avec GitLab (September 2016)](https://www.youtube.com/watch?v=URcMBXjIr24&t=13s). +- [GitLab CI for Minecraft Plugins (July 2016)](https://www.youtube.com/watch?v=Z4pcI9F8yf8). + +### Example Projects + +[`review-apps-nginx`](https://gitlab.com/gitlab-examples/review-apps-nginx/) provides an example of using Review Apps. + +Other example projects are available at the [`gitlab-examples`](https://gitlab.com/gitlab-examples) group. + +### Why GitLab CI/CD? + +The following articles explain reasons why you might use GitLab CI/CD for your CI/CD infrastructure: + +- [Why we chose GitLab CI for our CI/CD solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/). +- [Building our web-app on GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/). + +See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJaIOzCX4Vqg3dlwfELC3u2jEeCBbDk) presentation. ## Breaking changes -- [CI variables renaming for GitLab 9.0](variables/README.md#9-0-renaming) Read about the +As GitLab CI/CD has evolved, certain breaking changes have been necessary. These are: + +- [CI variables renaming for GitLab 9.0](variables/README.md#gitlab-90-renaming). Read about the deprecated CI variables and what you should use for GitLab 9.0+. -- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md) - Read about what changed in GitLab 8.12 and how that affects your jobs. +- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md). + See what changed in GitLab 8.12 and how that affects your jobs. There's a new way to access your Git submodules and LFS objects in jobs. diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md index dbe3d9b62c9..e079483e2b5 100644 --- a/doc/ci/caching/index.md +++ b/doc/ci/caching/index.md @@ -52,7 +52,7 @@ artifacts are also available in between stages within a pipeline. So if you build your application by downloading all the required modules, you might want to declare them as artifacts so that each subsequent stage can depend on them being there. There are some optimizations like declaring an -[expiry time](../yaml/README.md#artifacts-expire_in) so you don't keep artifacts +[expiry time](../yaml/README.md#artifactsexpire_in) so you don't keep artifacts around too long, and using [dependencies](../yaml/README.md#dependencies) to control exactly where artifacts are passed around. @@ -87,7 +87,7 @@ cache, when declaring `cache` in your jobs, use one or a mix of the following: that share their cache. - [Use sticky Runners](../runners/README.md#locking-a-specific-runner-from-being-enabled-for-other-projects) that will be only available to a particular project. -- [Use a `key`](../yaml/README.md#cache-key) that fits your workflow (e.g., +- [Use a `key`](../yaml/README.md#cachekey) that fits your workflow (e.g., different caches on each branch). For that, you can take advantage of the [CI/CD predefined variables](../variables/README.md#predefined-environment-variables). @@ -169,7 +169,7 @@ job: ``` For more fine tuning, read also about the -[`cache: policy`](../yaml/README.md#cache-policy). +[`cache: policy`](../yaml/README.md#cachepolicy). ## Common use cases diff --git a/doc/ci/chatops/README.md b/doc/ci/chatops/README.md new file mode 100644 index 00000000000..6ad1df7bb2a --- /dev/null +++ b/doc/ci/chatops/README.md @@ -0,0 +1,61 @@ +# GitLab ChatOps + +> **Notes:** +> +> * [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9. +> +> * ChatOps is currently in alpha, with some important features missing like access control. + +GitLab ChatOps provides a method to interact with CI/CD jobs through chat services like Slack. Many organizations' discussion, collaboration, and troubleshooting is taking place in chat services these days, and having a method to run CI/CD jobs with output posted back to the channel can significantly augment a team's workflow. + +## How it works + +GitLab ChatOps is built upon two existing features, [GitLab CI/CD](../README.md) and [Slack Slash Commmands](../../user/project/integrations/slack_slash_commands.md). + +A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [.gitlab-ci.yml](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.html#predefined-variables-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in. + +After the job has finished, its output is sent back to Slack provided it has completed within 30 minutes. If a job takes more than 30 minutes to run it must use the Slack API to manually send data back to a channel. + +[Developer access and above](../../user/permissions.html#project-members-permissions) is required to use the `run` command. If a job should not be able to be triggered from chat, it can be set to `except: [chat]`. + +## Creating a ChatOps CI job + +Since ChatOps is built upon GitLab CI/CD, the job has all the same features and functions available. There a few best practices to consider however when creating ChatOps jobs: + +* It is strongly recommended to set `only: [chat]` so the job does not run as part of the standard CI pipeline. +* If the job is set to `when: manual`, the pipeline will be created however the job will wait to be started. +* It is important to keep in mind that there is very limited support for access control. If the user who triggered the slash command is a developer in the project, the job will run. The job itself can utilize existing [CI/CD variables](../variables/README.html#predefined-environment-variables) like `GITLAB_USER_ID` to perform additional rights validation, however these variables can be [overridden](https://docs.gitlab.com/ce/ci/variables/README.html#priority-of-variables). + +### Controlling the ChatOps reply + +For jobs with a single command, its output is automatically sent back to the channel as a reply. For example the chat reply of the following job is simply `Hello World.` + +```yaml +hello-world: + stage: chatops + only: [chat] + script: + - echo "Hello World." +``` + +Jobs that contain multiple commands, or have a `before_script`, include additional content in the chat reply. In these cases both the commands and their output are included, with the commands wrapped in ANSI colors codes. + +To selectively reply with the output of one command, its output must be bounded by the `chat_reply` section. For example, the following job will list the files in the current directory. + +```yaml +ls: + stage: chatops + only: [chat] + script: + - echo "This command will not be shown." + - echo -e "section_start:$( date +%s ):chat_reply\r\033[0K\n$( ls -la )\nsection_end:$( date +%s ):chat_reply\r\033[0K" +``` + +## GitLab ChatOps icon + +Say Hi to our ChatOps bot. +You can find and download the official GitLab ChatOps icon here. + +![GitLab ChatOps bot icon](img/gitlab-chatops-icon-small.png) + +[Download bigger image](img/gitlab-chatops-icon.png) diff --git a/doc/ci/chatops/img/gitlab-chatops-icon-small.png b/doc/ci/chatops/img/gitlab-chatops-icon-small.png Binary files differnew file mode 100644 index 00000000000..71cc5dba5cf --- /dev/null +++ b/doc/ci/chatops/img/gitlab-chatops-icon-small.png diff --git a/doc/ci/chatops/img/gitlab-chatops-icon.png b/doc/ci/chatops/img/gitlab-chatops-icon.png Binary files differnew file mode 100644 index 00000000000..3ba8bd308e3 --- /dev/null +++ b/doc/ci/chatops/img/gitlab-chatops-icon.png diff --git a/doc/ci/docker/README.md b/doc/ci/docker/README.md index 8ae80b2bc02..446f5b54f0c 100644 --- a/doc/ci/docker/README.md +++ b/doc/ci/docker/README.md @@ -4,6 +4,8 @@ comments: false # Docker integration -- [Using Docker Images](using_docker_images.md) -- [Using Docker Build](using_docker_build.md) -- [Using kaniko](using_kaniko.md) +The following documentation is available for using GitLab CI/CD with Docker: + +- [Using Docker images](using_docker_images.md). +- [Building Docker images with GitLab CI/CD](using_docker_build.md). +- [Building images with kaniko and GitLab CI/CD](using_kaniko.md). diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 959271d8abc..3314c76d234 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -391,9 +391,9 @@ CI jobs: from `Dockerfile` that may be overridden in `.gitlab-ci.yml`) 1. The Runner attaches itself to a running container. 1. The Runner prepares a script (the combination of - [`before_script`](../yaml/README.md#before_script), + [`before_script`](../yaml/README.md#before_script-and-after_script), [`script`](../yaml/README.md#script), - and [`after_script`](../yaml/README.md#after_script)). + and [`after_script`](../yaml/README.md#before_script-and-after_script)). 1. The Runner sends the script to the container's shell STDIN and receives the output. diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 6a9917f6430..fe66f7e3c28 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -103,7 +103,7 @@ the Git SHA and environment name. To sum up, with the above `.gitlab-ci.yml` we have achieved that: - All branches will run the `test` and `build` jobs. -- The `deploy_staging` job will run [only](yaml/README.md#only) on the `master` +- The `deploy_staging` job will run [only](yaml/README.md#only-and-except-simplified) on the `master` branch which means all merge requests that are created from branches don't get to deploy to the staging server - When a merge request is merged, all jobs will run and the `deploy_staging` @@ -401,7 +401,7 @@ Let's briefly see where URL that's defined in the environments is exposed. ## Making use of the environment URL -The [environment URL](yaml/README.md#environments-url) is exposed in a few +The [environment URL](yaml/README.md#environmenturl) is exposed in a few places within GitLab. | In a merge request widget as a link | In the Environments view as a button | In the Deployments view as a button | @@ -619,9 +619,9 @@ Below are some links you may find interesting: [deployments]: #deployments [permissions]: ../user/permissions.md [variables]: variables/README.md -[env-name]: yaml/README.md#environment-name -[only]: yaml/README.md#only-and-except -[onstop]: yaml/README.md#environment-on_stop +[env-name]: yaml/README.md#environmentname +[only]: yaml/README.md#only-and-except-simplified +[onstop]: yaml/README.md#environmenton_stop [ce-7015]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7015 [gitlab-flow]: ../workflow/gitlab_flow.md [gitlab runner]: https://docs.gitlab.com/runner/ diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md index 9f3b8d9ad14..d7afe11f41f 100644 --- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md +++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md @@ -89,7 +89,7 @@ page and to interact with them - for example, to click on the link back to the h The simple test shown above can already give us a lot of confidence if it passes: we know our deployment has succeeded, that the elements are visible on the page and that actual browsers can interact with it, and that routing -works as expected. And all that in just 10 lines with gratituous whitespace! Add to that succeeding +works as expected. And all that in just 10 lines with gratuitous whitespace! Add to that succeeding unit tests and a successfully completed pipeline, and you can be fairly confident that the dependency upgrade did not break anything without even having to look at your website. diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md index c0346d78141..f24e79355aa 100644 --- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md +++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md @@ -75,7 +75,7 @@ When we call `mix` command, we'll pass two arguments: - The task we want it to run: `phoenix.new` - And the parameter `phoenix.new` requires, which is the name of the new project. In this case, -we're calling it `hello_gitlab_ci`, but you're free to set your own name: + we're calling it `hello_gitlab_ci`, but you're free to set your own name: ```bash mix phoenix.new hello_gitlab_ci @@ -249,7 +249,7 @@ project. ![Set up CI](img/setup-ci.png) - On next screen, we can select a template ready to go. Click on **Apply a GitLab CI/CD Yaml -template** and select **Elixir**: + template** and select **Elixir**: ![Select template](img/select-template.png) diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md index b7b5c660586..2af0a03cbe4 100644 --- a/doc/ci/merge_request_pipelines/index.md +++ b/doc/ci/merge_request_pipelines/index.md @@ -106,10 +106,10 @@ flow, external contributors follow the following steps: 1. Fork a parent project. 1. Create a merge request from the forked project that targets the `master` branch -in the parent project. + in the parent project. 1. A pipeline runs on the merge request. 1. A maintainer from the parent project checks the pipeline result, and merge -into a target branch if the latest pipeline has passed. + into a target branch if the latest pipeline has passed. Currently, those pipelines are created in a **forked** project, not in the parent project. This means you cannot completely trust the pipeline result, diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index c41f3b7e82d..4f3106c6dc6 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -197,7 +197,7 @@ stage has a job with a manual action. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4. -When you do not want to run a job immediately, you can [delay the job to run after a certain period](yaml/README.md#when-delayed). +When you do not want to run a job immediately, you can [delay the job to run after a certain period](yaml/README.md#whendelayed). This is especially useful for timed incremental rollout that new code is rolled out gradually. For example, if you start rolling out new code and users do not experience trouble, GitLab automatically completes the deployment from 0% to 100%. Alternatively, if you start rolling out and you noticed that a few users experience trouble with the version, diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index 0c3b0bf6990..ff63d0bd69f 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -92,7 +92,7 @@ to access it. This is where an SSH key pair comes in handy. ``` NOTE: **Note:** - The [`before_script`](../yaml/README.md#before-script) can be set globally + The [`before_script`](../yaml/README.md#before_script-and-after_script) can be set globally or per-job. 1. Make sure the private server's [SSH host keys are verified](#verifying-the-ssh-host-keys). diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 7498617ed2c..6fe352df48a 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -53,6 +53,8 @@ future GitLab releases.** | Variable | GitLab | Runner | Description | |-------------------------------------------|--------|--------|-------------| | **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job | +| **CHAT_INPUT** | 10.6 | all | Additional arguments passed in the [ChatOps](../chatops/README.md) command | +| **CHAT_CHANNEL** | 10.6 | all | Source chat channel which triggered the [ChatOps](../chatops/README.md) command | | **CI** | all | 0.4 | Mark that job is executed in CI environment | | **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. | | **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. | @@ -84,10 +86,12 @@ future GitLab releases.** | **CI_MERGE_REQUEST_PROJECT_URL** | 11.6 | all | The URL of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`) | | **CI_MERGE_REQUEST_REF_PATH** | 11.6 | all | The ref path of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`) | | **CI_MERGE_REQUEST_SOURCE_BRANCH_NAME** | 11.6 | all | The source branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_SOURCE_BRANCH_SHA** | 11.9 | all | The HEAD sha of the source branch of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_SOURCE_PROJECT_ID** | 11.6 | all | The ID of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_SOURCE_PROJECT_PATH** | 11.6 | all | The path of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_SOURCE_PROJECT_URL** | 11.6 | all | The URL of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_TARGET_BRANCH_SHA** | 11.9 | all | The HEAD sha of the target branch of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | | **CI_NODE_INDEX** | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. | | **CI_NODE_TOTAL** | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. | | **CI_API_V4_URL** | 11.7 | all | The GitLab API v4 root URL | diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 31f99cd5e68..e5668c140fa 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1,7 +1,7 @@ -# Configuration of your jobs with .gitlab-ci.yml +# Configuration of your pipelines with .gitlab-ci.yml This document describes the usage of `.gitlab-ci.yml`, the file that is used by -GitLab Runner to manage your project's jobs. +GitLab Runner to manage your project's pipelines. From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML) file (`.gitlab-ci.yml`) for the project configuration. It is placed in the root @@ -1206,7 +1206,7 @@ job: `expire_in` allows you to specify how long artifacts should live before they expire and therefore deleted, counting from the time they are uploaded and stored on GitLab. If the expiry time is not defined, it defaults to the -[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration) +[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only) (30 days by default, forever on GitLab.com). You can use the **Keep** button on the job page to override expiration and @@ -1245,7 +1245,7 @@ this with [JUnit reports](#artifactsreportsjunit). NOTE: **Note:** The test reports are collected regardless of the job results (success or failure). -You can use [`artifacts:expire_in`](#artifacts-expire_in) to set up an expiration +You can use [`artifacts:expire_in`](#artifactsexpire_in) to set up an expiration date for their artifacts. NOTE: **Note:** @@ -1425,7 +1425,7 @@ deploy: > Introduced in GitLab 10.3. If the artifacts of the job that is set as a dependency have been -[expired](#artifacts-expire_in) or +[expired](#artifactsexpire_in) or [erased](../../user/project/pipelines/job_artifacts.md#erasing-artifacts), then the dependent job will fail. diff --git a/doc/customization/system_header_and_footer_messages.md b/doc/customization/system_header_and_footer_messages.md new file mode 100644 index 00000000000..cf7d8b3f3e8 --- /dev/null +++ b/doc/customization/system_header_and_footer_messages.md @@ -0,0 +1,18 @@ +# Adding a system message to every page + +> [Introduced][ee-1283] in [GitLab Premium][eep] 10.7. + +Navigate to the **Admin** area and go to the **Appearance** page. + +Under **System header and footer** insert your header message and/or footer message. +Both background and font color of the header and footer are customizable. + +![appearance](system_header_and_footer_messages/appearance.png) + +After saving, all GitLab pages will contain the custom system header and/or footer messages: + +![custom_header_footer](system_header_and_footer_messages/custom_header_footer.png) + +The GitLab sign in page will also show the header and the footer messages: + +![sign_up_custom_header_and_footer](system_header_and_footer_messages/sign_up_custom_header_and_footer.png) diff --git a/doc/customization/system_header_and_footer_messages/appearance.png b/doc/customization/system_header_and_footer_messages/appearance.png Binary files differnew file mode 100644 index 00000000000..14667f751c4 --- /dev/null +++ b/doc/customization/system_header_and_footer_messages/appearance.png diff --git a/doc/customization/system_header_and_footer_messages/custom_header_footer.png b/doc/customization/system_header_and_footer_messages/custom_header_footer.png Binary files differnew file mode 100644 index 00000000000..691ca8a3484 --- /dev/null +++ b/doc/customization/system_header_and_footer_messages/custom_header_footer.png diff --git a/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png b/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png Binary files differnew file mode 100644 index 00000000000..a9c59bc9f62 --- /dev/null +++ b/doc/customization/system_header_and_footer_messages/sign_up_custom_header_and_footer.png diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index 95722c027ba..501092ff2aa 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -12,24 +12,34 @@ add a `HTTP_PRIVATE_TOKEN` header. ### Authorization Fields can be authorized using the same abilities used in the Rails -app. This can be done using the `authorize` helper: +app. This can be done by supplying the `authorize` option: ```ruby module Types class QueryType < BaseObject graphql_name 'Query' - field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do - authorize :read_project - end + field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver, authorize: :read_project end +end +``` + +Fields can be authorized against multiple abilities, in which case all +ability checks must pass. This requires explicitly passing a block to `field`: + +```ruby +field :project, Types::ProjectType, null: true, resolver: Resolvers::ProjectResolver do + authorize [:read_project, :another_ability] +end ``` The object found by the resolve call is used for authorization. -This works for authorizing a single record, for authorizing -collections, we should only load what the currently authenticated user -is allowed to view. Preferably we use our existing finders for that. +TIP: **Tip:** +When authorizing collections, try to load only what the currently +authenticated user is allowed to view with our existing finders first. +This minimizes database queries and unnecessary authorization checks of +the loaded records. ## Types diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 63574b28edc..e22552fd3a3 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -58,7 +58,7 @@ GitLab can be considered to have two layers from a process perspective: - [Omnibus configuration options](https://gitlab.com/gitlab-org/gitaly/tree/master/doc/configuration) - Layer: Core Service (Data) -Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab. (Think GitLab.com or High Availablity Deployments) As of 11.3.0, This service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly). +Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab (Think GitLab.com or High Availability Deployments). As of 11.3.0, this service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly). ### gitlab-monitor @@ -174,7 +174,7 @@ When making a request to an HTTP Endpoint (Think `/users/sign_in`) the request w - nginx - Acts as our first line reverse proxy - gitlab-workhorse - This determines if it needs to go to the Rails application or somewhere else to reduce load on unicorn. - unicorn - Since this is a web request, and it needs to access the application it will go to Unicorn. -- Postgres/Gitaly/Redis - Depending on the type of request, it may hit these services to store or retreive data. +- Postgres/Gitaly/Redis - Depending on the type of request, it may hit these services to store or retrieve data. ### GitLab Git Request Cycle diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md index fed772b9240..3ca841353e6 100644 --- a/doc/development/automatic_ce_ee_merge.md +++ b/doc/development/automatic_ce_ee_merge.md @@ -115,16 +115,16 @@ Now, every time you create an MR for CE and EE: 1. Continue cherry-picking: `git cherry-pick --continue` 1. Push to EE: `git push origin branch-example-ee` 1. Create the EE-equivalent MR and link to the CE MR from the -description "Ports [CE-MR-LINK] to EE" + description "Ports [CE-MR-LINK] to EE" 1. Once all the jobs are passing in both CE and EE, you've addressed the -feedback from your own team, and got them approved, the merge requests can be merged. + feedback from your own team, and got them approved, the merge requests can be merged. 1. When both MRs are ready, the EE merge request will be merged first, and the -CE-equivalent will be merged next. + CE-equivalent will be merged next. **Important notes:** - The commit SHA can be easily found from the GitLab UI. From a merge request, -open the tab **Commits** and click the copy icon to copy the commit SHA. + open the tab **Commits** and click the copy icon to copy the commit SHA. - To cherry-pick a **commit range**, such as [A > B > C > D] use: ```shell @@ -140,7 +140,7 @@ open the tab **Commits** and click the copy icon to copy the commit SHA. ``` - To cherry-pick a **merge commit**, use the flag `-m 1`. For example, suppose that the -merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`: + merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`: ```shell git cherry-pick -m 1 138f5e2f20289bb376caffa0303adb0cac859ce1 @@ -163,12 +163,12 @@ merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`: commits and you want to cherry-pick all but the merge commit. - If you push more commits to the CE branch, you can safely repeat the procedure -to cherry-pick them to the EE-equivalent branch. You can do that as many times as -necessary, using the same CE and EE branches. + to cherry-pick them to the EE-equivalent branch. You can do that as many times as + necessary, using the same CE and EE branches. - If you submitted the merge request to the CE repo and the `ee-compat-check` job passed, -you are not required to submit the EE-equivalent MR, but it's still recommended. If the -job failed, you are required to submit the EE MR so that you can fix the conflicts in EE -before merging your changes into CE. + you are not required to submit the EE-equivalent MR, but it's still recommended. If the + job failed, you are required to submit the EE MR so that you can fix the conflicts in EE + before merging your changes into CE. ## FAQ diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 1b591c7c322..f115045dbb7 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -203,7 +203,10 @@ first time. - Extract unrelated changes and refactorings into future merge requests/issues. - Seek to understand the reviewer's perspective. - Try to respond to every comment. -- Let the reviewer select the "Resolve discussion" buttons. +- The merge request author resolves only the discussions they have fully + addressed. If there's an open reply, an open discussion, a suggestion, + a question, or anything else, the discussion should be left to be resolved + by the reviewer. - Push commits based on earlier rounds of feedback as isolated commits to the branch. Do not squash until the branch is ready to merge. Reviewers should be able to read individual updates based on their earlier feedback. diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md index 7ac846e4c4d..f7a0fdbeb40 100644 --- a/doc/development/contributing/index.md +++ b/doc/development/contributing/index.md @@ -4,7 +4,7 @@ Thank you for your interest in contributing to GitLab. This guide details how to contribute to GitLab in a way that is easy for everyone. We want to create a welcoming environment for everyone who is interested in contributing. -Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our committment to an open and welcoming environment. +Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our commitment to an open and welcoming environment. For a first-time step-by-step guide to the contribution process, please see ["Contributing to GitLab"](https://about.gitlab.com/contributing/). diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md index c5344139ab4..4c53643ed9c 100644 --- a/doc/development/contributing/issue_workflow.md +++ b/doc/development/contributing/issue_workflow.md @@ -199,7 +199,7 @@ different way. We add the ~"Accepting merge requests" label to: - Low priority ~bug issues (i.e. we do not add it to the bugs that we want to -solve in the ~"Next Patch Release") + solve in the ~"Next Patch Release") - Small ~feature - Small ~"technical debt" issues @@ -300,17 +300,17 @@ below will make it easy to manage this, without unnecessary overhead. 1. Set weight for any issue at the earliest possible convenience 1. If you don't agree with a set weight, discuss with other developers until -consensus is reached about the weight + consensus is reached about the weight 1. Issue weights are an abstract measurement of complexity of the issue. Do not -relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring) -and something you want to avoid. + relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring) + and something you want to avoid. 1. Something that has a weight of 1 (or no weight) is really small and simple. -Something that is 9 is rewriting a large fundamental part of GitLab, -which might lead to many hard problems to solve. Changing some text in GitLab -is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9. + Something that is 9 is rewriting a large fundamental part of GitLab, + which might lead to many hard problems to solve. Changing some text in GitLab + is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9. 1. If something is very large, it should probably be split up in multiple -issues or chunks. You can simply not set the weight of a parent issue and set -weights to children issues. + issues or chunks. You can simply not set the weight of a parent issue and set + weights to children issues. ## Regression issues diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md index b5b325683a3..3f31fe5ca19 100644 --- a/doc/development/documentation/feature-change-workflow.md +++ b/doc/development/documentation/feature-change-workflow.md @@ -2,111 +2,178 @@ description: How to add docs for new or enhanced GitLab features. --- -# Documentation process at GitLab +# Documentation process for feature changes At GitLab, developers contribute new or updated documentation along with their code, but product managers and technical writers also have essential roles in the process. - **Developers**: Author/update documentation in the same MR as their code, and -merge it by the feature freeze for the assigned milestone. Request technical writer -assistance if needed. + merge it by the feature freeze for the assigned milestone. Request technical writer + assistance if needed. Other developers typically act as reviewers. - **Product Managers** (PMs): In the issue for all new and enhanced features, -confirm the documentation requirements, plus the mentioned feature description -and use cases, which can be reused in docs. They can bring in a technical -writer for discussion or help, and can be called upon themselves as a doc reviewer. + confirm the documentation requirements, plus the mentioned feature description + and use cases, which can be reused in docs. They can bring in a technical + writer for discussion or collaboration, and can be called upon themselves as a doc reviewer. - **Technical Writers**: Review doc requirements in issues, track issues and MRs -that contain docs changes, help with any questions throughout the authoring/editing process, -and review all new and updated docs content after it's merged (unless a pre-merge -review request is made). + that contain docs changes, help with any questions throughout the authoring/editing process, + work on special projects related to the documentation, and review all new and updated + docs content, whether before or after it is merged. -Beyond this process, any member of the GitLab community can also author documentation +Beyond this process, any member of the GitLab community can also author or request documentation improvements that are not associated with a new or changed feature. See the [Documentation improvement workflow](improvement-workflow.md). ## When documentation is required Documentation must be delivered whenever: -- A new or enhanced feature is shipped that impacts the user/admin experience -- There are changes to the UI or API -- A process, workflow, or previously documented feature is changed -- A feature is deprecated or removed +- A new or enhanced feature is shipped that impacts the user/admin experience. +- There are changes to the UI or API. +- A process, workflow, or previously documented feature is changed. +- A feature is deprecated or removed. + +For example, a UI restyling that offers no difference in functionality may require +documentation updates if screenshots are now needed, or need to be updated. Documentation is not required when a feature is changed on the backend -only and does not directly affect the way that any user or -administrator would interact with GitLab. For example, a UI restyling that offers -no difference in functionality may require documentation updates if screenshots -are now needed, or need to be updated. +only and does not directly affect the way that any user or administrator would +interact with GitLab. NOTE: **Note:** When revamping documentation, if unrelated to the feature change, this should be submitted in its own MR (using the [documentation improvement workflow](improvement-workflow.md)) so that we can ensure the more time-sensitive doc updates are merged with code by the freeze. +## Documentation requirements in feature issues + +Requirements for the documentation of a feature should be included as part of the +issue for planning that feature, in a Documentation section within the issue description. + +This section is provided as part of the Feature Proposal template and should be added +to the issue if it is not already present. + +Anyone can add these details, but the product manager who assigns the issue to a specific release +milestone will ensure these details are present and finalized by the time of that milestone's kickoff. +Developers, technical writers, and others may help further refine this plan at any time. + +### Details to include + +- What concepts and procedures should the docs guide and enable the user to understand or accomplish? +- To this end, what new page(s) are needed, if any? What pages/subsections need updates? Consider user, admin, and API doc changes and additions. +- For any guide or instruction set, should it help address a single use case, or be flexible to address a certain range of use cases? +- Do we need to update a previously recommended workflow? Should we link the new feature from various relevant locations? Consider all ways documentation should be affected. +- Are there any key terms or task descriptions that should be included so that the docs are found in relevant searches? +- Include suggested titles of any pages or subsections, if applicable. + ## Documenting a new or changed feature To follow a consistent workflow every month, documentation changes involve the Product Managers, the developer who shipped the feature, -and the Technical Writing team. Each role is described below. +and the technical writer for the DevOps stage. Each role is described below. + +The Documentation items in the GitLab CE/EE [Feature Proposal issue template](https://gitlab.com/gitlab-org/gitlab-ce/raw/template-improvements-for-documentation/.gitlab/issue_templates/Feature%20proposal.md) +and default merge request template will assist you with following this process. + +### Product Manager role -### 1. Product Manager's role +For issues requiring any new or updated documentation, the Product Manager (PM) +must: -The Product Manager (PM) should confirm or add the following items in the issue: +- Add the `Documentation` label. +- Confirm or add the [documentation requirements](#documentation-requirements). +- Ensure the issue contains any new or updated feature name, overview/description, + and use cases, as required per the [documentation structure and template](structure.md), when applicable. -- New or updated feature name, overview/description, and use cases, all required per the [Documentation structure and template](structure.md). -- The documentation requirements for the developer working on the docs. - - What new page, new subsection of an existing page, or other update to an existing page/subsection is needed. - - Just one page/section/update or multiple (perhaps there's an end user and admin change needing docs, or we need to update a previously recommended workflow, or we want to link the new feature from various places; consider and mention all ways documentation should be affected. - - Suggested title of any page or subsection, if applicable. -- Label the issue with `Documentation` and `docs:P1` in addition to the `Deliverable` label and correct milestone. +Everyone is encouraged to draft the requirements in the issue, but a product manager will +do the following: -Anyone is welcome to draft the items above in the issue, but a product manager must review and update them whenever the issue is assigned a specific milestone. +- When the issue is assigned a release milestone, review and update the Documentation details. +- By the kickoff, finalizie the Documentation details. -### 2. Developer's role +### Developer and maintainer roles + +#### Authoring As a developer, you must ship the documentation with the code of the feature that you are creating or updating. The documentation is an essential part of the product. +Technical writers are happy to help, as requested and planned on an issue-by-issue basis. + +Follow the process below unless otherwise agreed with the product manager and technical writer for a given issue: -- New and edited docs should be included in the MR introducing the code, and planned -in the issue that proposed the feature. However, if the new or changed doc requires -extensive collaboration or conversation, a separate, linked issue can be used for the planning process. +- Include any new and edited docs in the MR introducing the code. +- Use the Documentation requirements confirmed by the Product Manager in the + issue and discuss any further doc plans or ideas as needed. + - If the new or changed doc requires extensive collaboration or conversation, a separate, + linked issue can be used for the planning process. + - We are trying to avoid using a separate MR, so that the docs stay with the code, but the + Technical Writing team is interested in discussing any potential exceptions that may be suggested. - Use the [Documentation guidelines](index.md), as well as other resources linked from there, -including the [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/). + including the Documentation [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/). - If you need any help to choose the correct place for a doc, discuss a documentation -idea or outline, or request any other help, ping the Technical Writer for the relevant -[DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) -in your issue or MR, or write within `#docs` on the GitLab Slack. + idea or outline, or request any other help, ping the Technical Writer for the relevant + [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) + in your issue or MR, or write within `#docs` on the GitLab Slack. - The docs must be merged with the code **by the feature freeze date**, otherwise -- the feature cannot be included with the release.<!-- TODO: Policy/process for feature-flagged issues --> + the feature cannot be included with the release. A policy for documenting feature-flagged + issues is forthcoming and you are welcome to join the [discussion](https://gitlab.com/gitlab-org/gitlab-ce/issues/56813). + +#### Reviews and merging + +All reviewers can help ensure accuracy, clarity, completeness, and adherence to the plans in the issue, as well as the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html). + +- **Prior to merging**, documentation changes committed by the developer must be reviewed by: + + 1. **The code reviewer** for the MR, to confirm accuracy, clarity, and completeness. + 1. Optionally: Others involved in the work, such as other devs or the PM. + 1. Optionally: The technical writer for the DevOps stage. If not prior to merging, the technical writer will review after the merge. + This helps us ensure that the developer has time to merge good content by the freeze, and that it can be further refined by the release, if needed. + - To decide whether to request this review before the merge, consider the amount of time left before the code freeze, the size of the change, + and your degree of confidence in having users of an RC use your docs as written. + - Pre-merge tech writer reviews should be most common when the code is complete well in advance of the freeze and/or for larger documentation changes. + - You can request a review and if there is not sufficient time to complete it prior to the freeze, + the maintainer can merge the current doc changes (if complete) and create a follow-up doc review issue. + - The technical writer can also help decide what docs to merge before the freeze and whether to work on further changes in a follow up MR. + - **To request a pre-merge technical writer review**, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). + - **To request a post-merge technical writer review**, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR that makes the doc change. + 1. **The maintainer** who is assigned to merge the MR, to verify clarity, completeness, and quality, to the best of their ability. + +- Upon merging, if a technical writer review has not been performed and there is not yet a linked issue for a follow-up review, the maintainer should [create an issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review), link it from the MR, and + mention the original MR author in the new issue. Alternatively, the mainitainer can ask the MR author to create and link this issue before the MR is merged. + +- After merging, documentation changes are reviewed by: + + 1. The technical writer--**if** their review was not performed prior to the merge. + 2. Optionally: by the PM (for accuracy and to ensure it's consistent with the vision for how the product will be used). + Any party can raise the item to the PM for review at any point: the dev, the technical writer, or the PM, who can request/plan a review at the outset. + +### Technical Writer role + +#### Planning + +- The technical writer monitors the documentation needs of issues assigned to the current and next milestone + for their DevOps stage(s), and participates in any needed discussion on docs planning and requirements refinement + with the dev, PM, and others. +- The technical writer will review these requirements again upon the kickoff and provide feedback, as needed. + This is not a blocking review and developers should not wait to work on docs. -Prior to merge, documentation changes commited by the developer must be reviewed by: -* the person reviewing the code and merging the MR. -* optionally: others involved in the work (such as other devs, the PM, or a technical writer), if requested. +#### Collaboration -After merging, documentation changing are reviewed by: -* a technical writer (for clarity, structure, grammar, etc). -* optionally: by the PM (for accuracy and to ensure it's consistent with the vision for how the product will be used). -Any party can raise the item to the PM for review at any point: the dev, the technical writer, or the PM, who can request/plan a review at the outset. +By default, the developer will work on documentation changes independently, but +the developer, PM, or technicial writer can propose a broader collaboration for +any given issue. -### 3. Technical Writer's role +Additionally, technical writers are available for questions at any time. -**Planning** -- Once an issue contains a Documentation label and an upcoming milestone, a -technical writer reviews the listed documentation requirements, which should have -already been reviewed by the PM. (These are non-blocking reviews; developers should -not wait to work on docs.) -- Monitor the documentation needs of issues assigned to the current and next milestone, -and participate in any needed discussion on docs planning with the dev, PM, and others. +#### Review -**Review** - Techncial writers provide non-blocking reviews of all documentation changes, -typically after the change is merged. However, if the docs are ready in the MR while -we are awaiting other work in order to merge, the technical writer's review can commence early. + before or after the change is merged. However, if the docs are ready in the MR while + there's time before the freeze, the technical writer's review can commence early, on request. - The technical writer will confirm that the doc is clear, grammatically correct, -and discoverable, while avoiding redundancy, bad file locations, typos, broken links, -etc. The technical writer will review the documentation for the following, which -the developer and code reviewer should have already made a good-faith effort to ensure: + and discoverable, while avoiding redundancy, bad file locations, typos, broken links, + etc. The technical writer will review the documentation for the following, which + the developer and code reviewer should have already made a good-faith effort to ensure: - Clarity. - - Relevance (make sure the content is appropriate given the impact of the feature). - - Location (make sure the doc is in the correct dir and has the correct name). + - Adherence to the plans and goals in the issue. + - Location (make sure the docs are in the correct directorkes and has the correct name). - Syntax, typos, and broken links. - Improvements to the content. - - Accordance to the [Documentation Style Guide](styleguide.md) and [structure/template](structure.md). + - Accordance with the [Documentation Style Guide](styleguide.md), and [Structure and Template](structure.md) doc. diff --git a/doc/development/documentation/improvement-workflow.md b/doc/development/documentation/improvement-workflow.md index ef6392c6f7f..a12c3d5ea7b 100644 --- a/doc/development/documentation/improvement-workflow.md +++ b/doc/development/documentation/improvement-workflow.md @@ -9,26 +9,30 @@ Anyone can contribute a merge request or create an issue for GitLab's documentat This page covers the process for any contributions to GitLab's docs that are not part of feature development. If you are looking for information on updating GitLab's docs as is required with the development and release of a new feature -or feature enhancement, see the [feature-change documentation workflow](feature-change-workflow.md). +or feature enhancement, see the [documentation process for feature changes](feature-change-workflow.md). ## Who updates the docs Anyone can contribute! You can create a merge request with documentation when you find errors or other room for improvement in an existing doc, or when you -have an idea for all-new documentation that would help a GitLab user or admin -to achieve or improve their DevOps workflows. +have an idea for all-new documentation that would help a GitLab user or administrator +to accomplish their work with GitLab. ## How to update the docs -- Follow the described standards and processes listed on the [GitLab Documentation guidelines](index.md) page, -including linked resources: the [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/). -- Follow GitLab's [Merge Request Guidelines](../contributing/merge_request_workflow.md#merge-request-guidelines). -- If you need any help to choose the correct place for a doc, discuss a documentation +1. Click "Edit this Page" at the bottom of any page on docs.gitlab.com, or navigate to + one of the repositories and doc paths listed on the [GitLab Documentation guidelines](index.md) page. + Work in a fork if you do not have developer access to the GitLab project. +1. Follow the described standards and processes listed on that Guidelines page, + including the linked resources: the [Structure and template](structure.md) page, [Style Guide](styleguide.md), and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/). +1. Follow GitLab's [Merge Request Guidelines](../contributing/merge_request_workflow.md#merge-request-guidelines). + +If you need any help to choose the correct place for a doc, discuss a documentation idea or outline, or request any other help, ping the Technical Writer for the relevant [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) in your issue or MR, or write within `#docs` if you are a member of GitLab's Slack workspace. -## Merging +## Review and merging Anyone with master access to the affected GitLab project can merge documentation changes. This person must make a good-faith effort to ensure that the content is clear @@ -38,12 +42,22 @@ that it meets the [Documentation Guidelines](index.md) and [Style Guide](stylegu If the author or reviewer has any questions, or would like a techncial writer's review before merging, mention the writer who is assigned to the relevant [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). -## Technical Writer review +The process can involve the following parties/phases, and is replicated in the `Documentation` MR template for GitLab CE and EE, to help with following the process. + +**1. Primary Reviewer** - Review by a [code reviewer](https://about.gitlab.com/handbook/engineering/projects/) or other appropriate colleague to confirm accuracy, clarity, and completeness. This can be skipped for minor fixes without substantive content changes. + +**2. Technical Writer** - Optional - If not requested for this MR, must be scheduled post-merge. To request a pre-merge review, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). +To request a post-merge review, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR that makes the doc change. + +**3. Maintainer** -The technical writing team reviews changes after they are merged, unless a prior -review is requested. +1. Review by assigned maintainer, who can always request/require the above reviews. Maintainer review can occur before or after a technical writer review. +2. Ensure a release milestone of the format XX.Y is set. If the freeze for that release has begun, add the label `pick into <XX.Y>` unless this change is not required for the release. In that case, simply change the milestone. +3. If EE and CE MRs exist, merge the EE MR first, then the CE MR. +4. After merging, if there has not been a technical writer review and an issue for a follow-up review was not already created and linked from the MR, [create the issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR. ## Other ways to help If you have ideas for further documentation resources that would be best -considered/handled by technical writers, devs, and other SMEs, please create an issue. +considered/handled by technical writers, devs, and other SMEs, please [create an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Documentation) +using the Documentation template. diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 287a472d0d8..652fe3ea711 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -10,8 +10,8 @@ In addition to this page, the following resources to help craft and contribute d - [Style Guide](styleguide.md) - What belongs in the docs, language guidelines, and more. - [Structure and template](structure.md) - Learn the typical parts of a doc page and how to write each one. -- [Workflow](workflow.md) - A landing page for our key workflows: - - [Feature-change documentation workflow](feature-change-workflow.md) - Adding required documentation when developing a GitLab feature. +- [Workflows](workflow.md) - A landing page for our key workflows: + - [Documentation process for feature changes](feature-change-workflow.md) - Adding required documentation when developing a GitLab feature. - [Documentation improvement workflow](improvement-workflow.md) - New content not associated with a new feature. - [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) - A reference for the markdown implementation used by GitLab's documentation site and about.gitlab.com. - [Site architecture](site_architecture/index.md) - How docs.gitlab.com is built. diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md index 0ce5825fd61..ee3a9caf9a0 100644 --- a/doc/development/documentation/site_architecture/index.md +++ b/doc/development/documentation/site_architecture/index.md @@ -44,7 +44,7 @@ read through the [global navigation](global_nav.md) doc. The docs site is deployed to production with GitLab Pages, and previewed in merge requests with Review Apps. -The deployment aspects will be soon transfered from the [original document](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md) +The deployment aspects will be soon transferred from the [original document](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md) to this page. <!-- diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md index ee3bd5606a5..95b5fcd99a1 100644 --- a/doc/development/documentation/structure.md +++ b/doc/development/documentation/structure.md @@ -20,7 +20,7 @@ Every feature or use case document should include the following content in the f with exceptions and details noted below and in the template included on this page. - **Title**: Top-level heading with the feature name, or a use case name, which would start with -a verb, like Configuring, Enabling, etc. + a verb, like Configuring, Enabling, etc. - **Introduction**: A couple sentences about the subject matter and what's to be found on this page. - **Overview** Describe what it is, what it does, and in what context it should be used. - **Use cases**: describes real use case scenarios for that feature/configuration. @@ -95,7 +95,7 @@ Link each one to an appropriate place for more information. "Instructions" is usually not the name of the heading. This is the part of the document where you can include one or more sets of instructions, each to accomplish a specific task. Headers should describe the task the reader will achieve by following the instructions within, typically starting with a verb. -Larger instruction sets may have subsections covering specific phases of the process. +Larger instruction sets may have subsections covering specific phases of the process. - Write a step-by-step guide, with no gaps between the steps. - Start with an h2 (`##`), break complex steps into small steps using diff --git a/doc/development/documentation/workflow.md b/doc/development/documentation/workflow.md index 7c32c92b147..0abfe4b82a4 100644 --- a/doc/development/documentation/workflow.md +++ b/doc/development/documentation/workflow.md @@ -2,9 +2,9 @@ description: Learn the processes for contributing to GitLab's documentation. --- -# Documentation workflows at GitLab +# Documentation workflows -Documentation workflows at GitLab differ depending on the reason for the change. The two types of documentation changes are: +Documentation workflows at GitLab differ depending on the reason for the change: -- [Feature-change documentation workflow](feature-change-workflow.md) - The documentation is being created or updated as part of the development and release of a new or enhanced feature. This process involves the developer of the feature (who includes new/updated documentation files as part of the same merge request containing the feature's code) and also involves the product manager and technical writer who are listed for the feature's [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). +- [Documentation process for feature changes](feature-change-workflow.md) - The documentation is being created or updated as part of the development and release of a new or enhanced feature. This process involves the developer of the feature (who includes new/updated documentation files as part of the same merge request containing the feature's code) and also involves the product manager and technical writer who are listed for the feature's [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages). - [Documentation improvement workflow](improvement-workflow.md) - All documentation additions not associated with a feature release. Documentation is being created or updated to improve accuracy, completeness, ease of use, or any reason other than a feature change. Anyone (and everyone) can contribute a merge request for this type of change at any time. diff --git a/doc/development/fe_guide/development_process.md b/doc/development/fe_guide/development_process.md index 905668eef58..f3fdaa3b883 100644 --- a/doc/development/fe_guide/development_process.md +++ b/doc/development/fe_guide/development_process.md @@ -61,17 +61,19 @@ Please use your best judgement when to use it and please contribute new points t --- ### Share your work early + 1. Before writing code, ensure your vision of the architecture is aligned with -GitLab's architecture. + GitLab's architecture. 1. Add a diagram to the issue and ask a frontend architect in the slack channel `#fe_architectural` about it. ![Diagram of Issue Boards Architecture](img/boards_diagram.png) 1. Don't take more than one week between starting work on a feature and -sharing a Merge Request with a reviewer or a maintainer. + sharing a Merge Request with a reviewer or a maintainer. ### Vue features + 1. Follow the steps in [Vue.js Best Practices](vue.md) 1. Follow the style guide. 1. Only a handful of people are allowed to merge Vue related features. -Reach out to one of Vue experts early in this process. + Reach out to one of Vue experts early in this process. diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md index ddc6a3386c7..1f188c64fe4 100644 --- a/doc/development/fe_guide/droplab/plugins/filter.md +++ b/doc/development/fe_guide/droplab/plugins/filter.md @@ -9,7 +9,7 @@ Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or `D - `Filter` requires a config value for `template`. - `template` should be the key of the objects within your data array that you want to compare -to the user input string, for filtering. + to the user input string, for filtering. ```html <input href="#" id="trigger" data-dropdown-trigger="#list"> diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md index e229103e462..e4050213869 100644 --- a/doc/development/fe_guide/droplab/plugins/input_setter.md +++ b/doc/development/fe_guide/droplab/plugins/input_setter.md @@ -9,7 +9,7 @@ Add the `InputSetter` object to the plugins array of a `DropLab.prototype.init` - `InputSetter` requires a config value for `input` and `valueAttribute`. - `input` should be the DOM element that you want to manipulate. - `valueAttribute` should be a string that is the name of an attribute on your list items that is used to get the value -to update the `input` element with. + to update the `input` element with. You can also set the `InputSetter` config to an array of objects, which will allow you to update multiple elements. diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md index f55f01720fd..3290f29530a 100644 --- a/doc/development/fe_guide/graphql.md +++ b/doc/development/fe_guide/graphql.md @@ -9,7 +9,7 @@ read more about [Feature Flags][feature-flags]. ## Apollo Client To save duplicated clients getting created in different apps, we have a -[default client][defualt-client] that should be used. This setups the +[default client][default-client] that should be used. This setups the Apollo client with the correct URL and also sets the CSRF headers. ## GraphQL Queries @@ -27,11 +27,11 @@ the Vue application is mounted. ```javascript import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import defaultClient from '~/lib/graphql'; +import createDefaultClient from '~/lib/graphql'; Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient, + defaultClient: createDefaultClient(), }); new Vue({ @@ -43,6 +43,29 @@ new Vue({ Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-apollo-docs]. +### Local state with `apollo-link-state` + +It is possible to use our Apollo setup with [apollo-link-state][apollo-link-state] by passing +in the client state object when creating the default client. + +```javascript +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient({ + defaults: { + testing: true, + }, + resolvers: { + ... + }, + }), +}); +``` + ### Testing With [Vue test utils][vue-test-utils] it is easy to quickly test components that @@ -81,3 +104,4 @@ Read more about the [Apollo] client in the [Apollo documentation][apollo-client- [default-client]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/lib/graphql.js [apollo-client-docs]: https://www.apollographql.com/docs/tutorial/client.html [vue-test-utils]: https://vue-test-utils.vuejs.org/ +[apollo-link-state]: https://www.apollographql.com/docs/link/links/state.html diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md index 0aba38aca8c..2628e95dbc1 100644 --- a/doc/development/fe_guide/performance.md +++ b/doc/development/fe_guide/performance.md @@ -5,6 +5,7 @@ ### Realtime Components When writing code for realtime features we have to keep a couple of things in mind: + 1. Do not overload the server with requests. 1. It should feel realtime. @@ -12,16 +13,16 @@ Thus, we must strike a balance between sending requests and the feeling of realt Use the following rules when creating realtime solutions. 1. The server will tell you how much to poll by sending `Poll-Interval` in the header. -Use that as your polling interval. This way it is [easy for system administrators to change the -polling rate](../../administration/polling.md). -A `Poll-Interval: -1` means you should disable polling, and this must be implemented. + Use that as your polling interval. This way it is [easy for system administrators to change the + polling rate](../../administration/polling.md). + A `Poll-Interval: -1` means you should disable polling, and this must be implemented. 1. A response with HTTP status different from 2XX should disable polling as well. 1. Use a common library for polling. 1. Poll on active tabs only. Please use [Visibility](https://github.com/ai/visibilityjs). 1. Use regular polling intervals, do not use backoff polling, or jitter, as the interval will be -controlled by the server. + controlled by the server. 1. The backend code will most likely be using etags. You do not and should not check for status -`304 Not Modified`. The browser will transform it for you. + `304 Not Modified`. The browser will transform it for you. ### Lazy Loading Images diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 1e0529262ad..060cd8baf7f 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -14,9 +14,9 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/ #### ESlint 1. **Never** disable eslint rules unless you have a good reason. -You may see a lot of legacy files with `/* eslint-disable some-rule, some-other-rule */` -at the top, but legacy files are a special case. Any time you develop a new feature or -refactor an existing one, you should abide by the eslint rules. + You may see a lot of legacy files with `/* eslint-disable some-rule, some-other-rule */` + at the top, but legacy files are a special case. Any time you develop a new feature or + refactor an existing one, you should abide by the eslint rules. 1. **Never Ever EVER** disable eslint globally for a file @@ -53,7 +53,7 @@ refactor an existing one, you should abide by the eslint rules. 1. [class-methods-use-this][eslint-this] 1. When they are needed _always_ place ESlint directive comment blocks on the first line of a script, -followed by any global declarations, then a blank newline prior to any imports or code. + followed by any global declarations, then a blank newline prior to any imports or code. ```javascript // bad @@ -125,8 +125,8 @@ followed by any global declarations, then a blank newline prior to any imports o ``` 1. Relative paths: when importing a module in the same directory, a child -directory, or an immediate parent directory prefer relative paths. When -importing a module which is two or more levels up, prefer either `~/` or `ee/`. + directory, or an immediate parent directory prefer relative paths. When + importing a module which is two or more levels up, prefer either `~/` or `ee/`. In **app/assets/javascripts/my-feature/subdir**: @@ -163,9 +163,9 @@ importing a module which is two or more levels up, prefer either `~/` or `ee/`. ``` 1. Avoid using IIFE. Although we have a lot of examples of files which wrap their -contents in IIFEs (immediately-invoked function expressions), -this is no longer necessary after the transition from Sprockets to webpack. -Do not use them anymore and feel free to remove them when refactoring legacy code. + contents in IIFEs (immediately-invoked function expressions), + this is no longer necessary after the transition from Sprockets to webpack. + Do not use them anymore and feel free to remove them when refactoring legacy code. 1. Avoid adding to the global namespace. ```javascript @@ -484,8 +484,8 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation. ``` 1. Default key should be provided if the prop is not required. -_Note:_ There are some scenarios where we need to check for the existence of the property. -On those a default key should not be provided. + _Note:_ There are some scenarios where we need to check for the existence of the property. + On those a default key should not be provided. ```javascript // good diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index 60f322c6e75..3cd70bd63fa 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -223,6 +223,7 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e ## Vue.js Expert Role One should apply to be a Vue.js expert by opening an MR when the Merge Request's they create and review show: + - Deep understanding of Vue and Vuex reactivy - Vue and Vuex code are structured according to both official and our guidelines - Full understanding of testing a Vue and Vuex application diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index 6054873cb46..dd338f6f67d 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -47,6 +47,7 @@ are very appreciative of the work done by translators and proofreaders! - Hungarian - Proofreaders needed. - Indonesian + - Adi Ferdian - [GitLab](https://gitlab.com/adiferd), [Crowdin](https://crowdin.com/profile/adiferd) - Ahmad Naufal Mukhtar - [GitLab](https://gitlab.com/anaufalm), [Crowdin](https://crowdin.com/profile/anaufalm) - Italian - Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo) diff --git a/doc/development/import_export.md b/doc/development/import_export.md index e2108605d54..f7f48b03651 100644 --- a/doc/development/import_export.md +++ b/doc/development/import_export.md @@ -222,7 +222,7 @@ bundle exec rake gitlab:import_export:bump_version ### Renaming columns or models -This is a relatively common occurence that will require a version bump. +This is a relatively common occurrence that will require a version bump. There is also the _RC problem_ - GitLab.com runs an RC, prior to any customers, meaning that we want to bump the version up in the next version (or patch release). diff --git a/doc/development/logging.md b/doc/development/logging.md index 5c1d96b9e0c..d61441813b2 100644 --- a/doc/development/logging.md +++ b/doc/development/logging.md @@ -32,8 +32,8 @@ These logs suffer from a number of problems: 1. They often lack timestamps or other contextual information (e.g. project ID, user) 2. They may span multiple lines, which make them hard to find via Elasticsearch. 3. They lack a common structure, which make them hard to parse by log -forwarders, such as Logstash or Fluentd. This also makes them hard to -search. + forwarders, such as Logstash or Fluentd. This also makes them hard to + search. Note that currently on GitLab.com, any messages in `production.log` will NOT get indexed by Elasticsearch due to the sheer volume and noise. They @@ -61,12 +61,12 @@ importer. You want to log issues created, merge requests, etc. as the importer progresses. Here's what to do: 1. Look at [the list of GitLab Logs](../administration/logs.md) to see -if your log message might belong with one of the existing log files. + if your log message might belong with one of the existing log files. 1. If there isn't a good place, consider creating a new filename, but -check with a maintainer if it makes sense to do so. A log file should -make it easy for people to search pertinent logs in one place. For -example, `geo.log` contains all logs pertaining to GitLab Geo. -To create a new file: + check with a maintainer if it makes sense to do so. A log file should + make it easy for people to search pertinent logs in one place. For + example, `geo.log` contains all logs pertaining to GitLab Geo. + To create a new file: 1. Choose a filename (e.g. `importer_json.log`). 1. Create a new subclass of `Gitlab::JsonLogger`: @@ -130,15 +130,15 @@ To create a new file: ## Additional steps with new log files 1. Consider log retention settings. By default, Omnibus will rotate any -logs in `/var/log/gitlab/gitlab-rails/*.log` every hour and [keep at -most 30 compressed files](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate). -On GitLab.com, that setting is only 6 compressed files. These settings should suffice -for most users, but you may need to tweak them in [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab). + logs in `/var/log/gitlab/gitlab-rails/*.log` every hour and [keep at + most 30 compressed files](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate). + On GitLab.com, that setting is only 6 compressed files. These settings should suffice + for most users, but you may need to tweak them in [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab). 1. If you add a new file, submit an issue to the [production -tracker](https://gitlab.com/gitlab-com/gl-infra/production/issues) or -a merge request to the [gitlab_fluentd](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd) -project. See [this example](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd/merge_requests/51/diffs). + tracker](https://gitlab.com/gitlab-com/gl-infra/production/issues) or + a merge request to the [gitlab_fluentd](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd) + project. See [this example](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd/merge_requests/51/diffs). 1. Be sure to update the [GitLab CE/EE documentation](../administration/logs.md) and the [GitLab.com -runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/howto/logging.md). + runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/howto/logging.md). diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md index 2a3a126ca5c..8420a504ec4 100644 --- a/doc/development/new_fe_guide/development/accessibility.md +++ b/doc/development/new_fe_guide/development/accessibility.md @@ -2,7 +2,7 @@ Using semantic HTML plays a key role when it comes to accessibility. ## Accessible Rich Internet Applications - ARIA -WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities. +WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities. > Note: It is [recommended][using-aria] to use semantic elements as the primary method to achieve accessibility rather than adding aria attributes. Adding aria attributes should be seen as a secondary method for creating accessible elements. @@ -15,6 +15,7 @@ Check the list of WAI-ARIA roles [here][roles] When using icons or images that aren't absolutely needed to understand the context, we should use `aria-hidden="true"`. On the other hand, if an icon is crucial to understand the context we should do one of the following: + 1. Use `aria-label` in the element with a meaningful description 1. Use `aria-labelledby` to point to an element that contains the explanation for that icon diff --git a/doc/development/new_fe_guide/style/html.md b/doc/development/new_fe_guide/style/html.md index 2d5b7d048ab..035fcbb28df 100644 --- a/doc/development/new_fe_guide/style/html.md +++ b/doc/development/new_fe_guide/style/html.md @@ -3,6 +3,7 @@ ## Buttons <a name="button-type"></a><a name="1.1"></a> + - [1.1](#button-type) **Use button type** Button tags requires a `type` attribute according to the [W3C HTML specification][button-type-spec]. ``` @@ -14,6 +15,7 @@ ``` <a name="button-role"></a><a name="1.2"></a> + - [1.2](#button-role) **Use button role for non buttons** If an HTML element has an onClick handler but is not a button, it should have `role="button"`. This is more [accessible][button-role-accessible]. ``` @@ -27,6 +29,7 @@ ## Links <a name="blank-links"></a><a name="2.1"></a> + - [2.1](#blank-links) **Use rel for target blank** Use `rel="noopener noreferrer"` whenever your links open in a new window i.e. `target="_blank"`. This prevents [the following][jitbit-target-blank] security vulnerability documented by JitBit ``` @@ -38,6 +41,7 @@ ``` <a name="fake-links"></a><a name="2.2"></a> + - [2.2](#fake-links) **Do not use fake links** Use a button tag if a link only invokes JavaScript click event handlers. This is more semantic. ``` diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/new_fe_guide/style/javascript.md index 922fd1e4ea4..7985b893c9e 100644 --- a/doc/development/new_fe_guide/style/javascript.md +++ b/doc/development/new_fe_guide/style/javascript.md @@ -10,6 +10,7 @@ You can run eslint locally by running `yarn eslint` ## Arrays <a name="avoid-foreach"></a><a name="1.1"></a> + - [1.1](#avoid-foreach) **Avoid ForEach when mutating data** Use `map`, `reduce` or `filter` instead of `forEach` when mutating data. This will minimize mutations in functions ([which is aligned with Airbnb's style guide][airbnb-minimize-mutations]) ``` @@ -17,7 +18,7 @@ You can run eslint locally by running `yarn eslint` users.forEach((user, index) => { user.id = index; }); - + // good const usersWithId = users.map((user, index) => { return Object.assign({}, user, { id: index }); @@ -27,6 +28,7 @@ You can run eslint locally by running `yarn eslint` ## Functions <a name="limit-params"></a><a name="2.1"></a> + - [2.1](#limit-params) **Limit number of parameters** If your function or method has more than 3 parameters, use an object as a parameter instead. ``` @@ -34,7 +36,7 @@ You can run eslint locally by running `yarn eslint` function a(p1, p2, p3) { // ... }; - + // good function a(p) { // ... @@ -44,6 +46,7 @@ You can run eslint locally by running `yarn eslint` ## Classes & constructors <a name="avoid-constructor-side-effects"></a><a name="3.1"></a> + - [3.1](#avoid-constructor-side-effects) **Avoid side effects in constructors** Avoid making some operations in the `constructor`, such as asynchronous calls, API requests and DOM manipulations. Prefer moving them into separate functions. This will make tests easier to write and code easier to maintain. ```javascript @@ -54,23 +57,24 @@ You can run eslint locally by running `yarn eslint` axios.get(this.config.endpoint) } } - + // good class myClass { constructor(config) { this.config = config; } - + makeRequest() { axios.get(this.config.endpoint) } } const instance = new myClass(); instance.makeRequest(); - + ``` <a name="avoid-classes-to-handle-dom-events"></a><a name="3.2"></a> + - [3.2](#avoid-classes-to-handle-dom-events) **Avoid classes to handle DOM events** If the only purpose of the class is to bind a DOM event and handle the callback, prefer using a function. ``` @@ -79,14 +83,14 @@ You can run eslint locally by running `yarn eslint` constructor(config) { this.config = config; } - + init() { document.addEventListener('click', () => {}); } } - + // good - + const myFunction = () => { document.addEventListener('click', () => { // handle callback here @@ -95,8 +99,9 @@ You can run eslint locally by running `yarn eslint` ``` <a name="element-container"></a><a name="3.3"></a> + - [3.3](#element-container) **Pass element container to constructor** When your class manipulates the DOM, receive the element container as a parameter. -This is more maintainable and performant. + This is more maintainable and performant. ``` // bad @@ -105,7 +110,7 @@ This is more maintainable and performant. document.querySelector('.b'); } } - + // good class a { constructor(options) { @@ -117,12 +122,13 @@ This is more maintainable and performant. ## Type Casting & Coercion <a name="use-parseint"></a><a name="4.1"></a> + - [4.1](#use-parseint) **Use ParseInt** Use `ParseInt` when converting a numeric string into a number. ``` // bad Number('10') - + // good parseInt('10', 10); ``` @@ -130,12 +136,13 @@ This is more maintainable and performant. ## CSS Selectors <a name="use-js-prefix"></a><a name="5.1"></a> + - [5.1](#use-js-prefix) **Use js prefix** If a CSS class is only being used in JavaScript as a reference to the element, prefix the class name with `js-` ``` // bad <button class="add-user"></button> - + // good <button class="js-add-user"></button> ``` @@ -143,44 +150,51 @@ This is more maintainable and performant. ## Modules <a name="use-absolute-paths"></a><a name="6.1"></a> + - [6.1](#use-absolute-paths) **Use absolute paths for nearby modules** Use absolute paths if the module you are importing is less than two levels up. ``` // bad import GitLabStyleGuide from '~/guides/GitLabStyleGuide'; - + // good import GitLabStyleGuide from '../GitLabStyleGuide'; ``` <a name="use-relative-paths"></a><a name="6.2"></a> + - [6.2](#use-relative-paths) **Use relative paths for distant modules** If the module you are importing is two or more levels up, use a relative path instead of an absolute path. ``` // bad import GitLabStyleGuide from '../../../guides/GitLabStyleGuide'; - + // good import GitLabStyleGuide from '~/GitLabStyleGuide'; ``` <a name="global-namespace"></a><a name="6.3"></a> + - [6.3](#global-namespace) **Do not add to global namespace** <a name="domcontentloaded"></a><a name="6.4"></a> + - [6.4](#domcontentloaded) **Do not use DOMContentLoaded in non-page modules** Imported modules should act the same each time they are loaded. `DOMContentLoaded` events are only allowed on modules loaded in the `/pages/*` directory because those are loaded dynamically with webpack. ## Security <a name="avoid-xss"></a><a name="7.1"></a> + - [7.1](#avoid-xss) **Avoid XSS** Do not use `innerHTML`, `append()` or `html()` to set content. It opens up too many vulnerabilities. ## ESLint <a name="disable-eslint-file"></a><a name="8.1"></a> + - [8.1](#disable-eslint-file) **Disabling ESLint in new files** Do not disable ESLint when creating new files. Existing files may have existing rules disabled due to legacy compatibility reasons but they are in the process of being refactored. <a name="disable-eslint-rule"></a><a name="8.2"></a> + - [8.2](#disable-eslint-rule) **Disabling ESLint rule** Do not disable specific ESLint rules. Due to technical debt, you may disable the following rules only if you are invoking/instantiating existing code modules - [no-new][no-new] diff --git a/doc/development/ordering_table_columns.md b/doc/development/ordering_table_columns.md index 3e49a65f5ab..cbfd05e731d 100644 --- a/doc/development/ordering_table_columns.md +++ b/doc/development/ordering_table_columns.md @@ -19,7 +19,7 @@ The space between rows is also subject to alignment padding. The `user_id` column takes only 4 bytes, and on 64-bit platform, 4 zeroes will be added for alignment padding, to allow storing the next row beginning with the "clear" word. -As a result, the actual size of each column would be (ommiting variable length +As a result, the actual size of each column would be (omitting variable length data and 24-byte tuple header): 8 bytes, variable, 8 bytes. This means that each row will require at least 16 bytes for the two 4-byte integers. If a table has a few rows this is not an issue. However, once you start storing millions of diff --git a/doc/development/performance.md b/doc/development/performance.md index 32970152911..2a287611bdf 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -410,6 +410,21 @@ there's nothing stopping somebody from doing this elsewhere in the code: SOME_CONSTANT = 'bar' ``` +## How to seed a database with millions of rows + +You might want millions of project rows in your local database, for example, +in order to compare relative query performance, or to reproduce a bug. You could +do this by hand with SQL commands, but since you have ActiveRecord models, you +might find using these gems more convenient: + +- [BulkInsert gem](https://github.com/jamis/bulk_insert) +- [ActiveRecord::PgGenerateSeries gem](https://github.com/ryu39/active_record-pg_generate_series) + +### Examples + +You may find some useful examples in this snippet: +https://gitlab.com/gitlab-org/gitlab-ce/snippets/33946 + [#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607 [yorickpeterse]: https://gitlab.com/yorickpeterse [anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern diff --git a/doc/development/policies.md b/doc/development/policies.md index 6a3b90f3604..c4ac42bb40a 100644 --- a/doc/development/policies.md +++ b/doc/development/policies.md @@ -88,10 +88,10 @@ An example debug output would look as follows: Each line represents a rule that was evaluated. There are a few things to note: 1. The `-` or `+` symbol indicates whether the rule block was evaluated to be -`false` or `true`, respectively. + `false` or `true`, respectively. 2. The number inside the brackets indicates the score. 3. The last part of the line (e.g. `@john : Issue/1`) shows the username -and subject for that rule. + and subject for that rule. Here you can see that the first four rules were evaluated `false` for which user and subject. For example, you can see in the last line that diff --git a/doc/development/polling.md b/doc/development/polling.md index 3b34f985cd4..76bb5ae7819 100644 --- a/doc/development/polling.md +++ b/doc/development/polling.md @@ -51,6 +51,7 @@ request path. By doing this we avoid query parameter ordering problems and make route matching easier. For more information see: + - [`Poll-Interval` header](fe_guide/performance.md#realtime-components) - [RFC 7232](https://tools.ietf.org/html/rfc7232) - [ETag proposal](https://gitlab.com/gitlab-org/gitlab-ce/issues/26926) diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 4cc500ed1b6..dcb32c89f65 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -73,6 +73,7 @@ To make sure that indices still fit. You could find great details in: ## Run tests In order to run the test you can use the following commands: + - `rake spec` to run the rspec suite - `rake karma` to run the karma test suite - `rake gitlab:test` to run all the tests diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index 4cc3812b0f0..2c8d488877b 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -358,16 +358,11 @@ range of inputs, might look like this: describe "#==" do using RSpec::Parameterized::TableSyntax - let(:project1) { create(:project) } - let(:project2) { create(:project) } where(:a, :b, :result) do 1 | 1 | true 1 | 2 | false true | true | true true | false | false - project1 | project1 | true - project2 | project2 | true - project 1 | project2 | false end with_them do @@ -380,6 +375,11 @@ describe "#==" do end ``` +CAUTION: **Caution:** +Only use simple values as input in the `where` block. Using procs, stateful +objects, FactoryBot-created objects etc. can lead to +[unexpected results](https://github.com/tomykaira/rspec-parameterized/issues/8). + ### Prometheus tests Prometheus metrics may be preserved from one test run to another. To ensure that metrics are diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md index e9f236c6b3a..a239dc84a1c 100644 --- a/doc/development/testing_guide/end_to_end_tests.md +++ b/doc/development/testing_guide/end_to_end_tests.md @@ -41,24 +41,24 @@ Currently, we are using _multi-project pipeline_-like approach to run QA pipelines. 1. Developer triggers a manual action, that can be found in CE / EE merge -requests. This starts a chain of pipelines in multiple projects. + requests. This starts a chain of pipelines in multiple projects. 1. The script being executed triggers a pipeline in [Omnibus GitLab][omnibus-gitlab] -and waits for the resulting status. We call this a _status attribution_. + and waits for the resulting status. We call this a _status attribution_. 1. GitLab packages are being built in the [Omnibus GitLab][omnibus-gitlab] -pipeline. Packages are then pushed to its Container Registry. + pipeline. Packages are then pushed to its Container Registry. 1. When packages are ready, and available in the registry, a final step in the -[Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new -[GitLab QA pipeline][gitlab-qa-pipelines]. It also waits for a resulting status. + [Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new + [GitLab QA pipeline][gitlab-qa-pipelines]. It also waits for a resulting status. 1. GitLab QA pulls images from the registry, spins-up containers and runs tests -against a test environment that has been just orchestrated by the `gitlab-qa` -tool. + against a test environment that has been just orchestrated by the `gitlab-qa` + tool. 1. The result of the [GitLab QA pipeline][gitlab-qa-pipelines] is being -propagated upstream, through Omnibus, back to the CE / EE merge request. + propagated upstream, through Omnibus, back to the CE / EE merge request. #### How do I write tests? diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 6012f1080ab..0470a071d39 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -141,14 +141,15 @@ module. GitLab has a custom `spyOnDependency` method which utilizes [babel-plugin-rewire](https://github.com/speedskater/babel-plugin-rewire) to achieve this. It can be used like so: -```javascript +```js // my_module.js import { visitUrl } from '~/lib/utils/url_utility'; export default function doSomething() { visitUrl('/foo/bar'); } - +``` +```js // my_module_spec.js import doSomething from '~/my_module'; diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md index 85b47f88cc4..703e342fc13 100644 --- a/doc/development/testing_guide/review_apps.md +++ b/doc/development/testing_guide/review_apps.md @@ -70,9 +70,8 @@ subgraph GCP `gitlab-review-apps` project you get a dedicated environment for your branch that's very close to what it would look in production. 1. Once the [`review-deploy`][review-deploy] job succeeds, you should be able to - use your Review App thanks to the direct link to it from the MR widget. The - default username is `root` and its password can be found in the 1Password - secure note named **gitlab-{ce,ee} Review App's root password**. + use your Review App thanks to the direct link to it from the MR widget. To log + into the Review App, see "Log into my Review App?" below. **Additional notes:** @@ -98,6 +97,17 @@ Note that both jobs first wait for the `review-deploy` job to be finished. ## How to? +### Log into my Review App? + +The default username is `root` and its password can be found in the 1Password +secure note named **gitlab-{ce,ee} Review App's root password**. + +### Enable a feature flag for my Review App? + +1. Open your Review App and log in as documented above. +1. Create a personal access token. +1. Enable the feature flag using the [Feature flag API](../../api/features.md). + ### Find my Review App slug? 1. Open the `review-deploy` job. diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md index 070b6477a7a..a7a3459719b 100644 --- a/doc/development/testing_guide/testing_levels.md +++ b/doc/development/testing_guide/testing_levels.md @@ -159,6 +159,10 @@ Every new feature should come with a [test plan]. > See [end-to-end tests](end_to_end_tests.md) for more information. +Note that `qa/spec` contains unit tests of the QA framework itself, not to be +confused with the application's [unit tests](#unit-tests) or +[end-to-end tests](#black-box-tests-at-the-system-level-aka-end-to-end-tests). + [multiple pieces]: ../architecture.md#components [GitLab Shell]: https://gitlab.com/gitlab-org/gitlab-shell [GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md index be14858f4c8..fa5be1d30f9 100644 --- a/doc/install/azure/index.md +++ b/doc/install/azure/index.md @@ -21,14 +21,14 @@ First, you'll need an account on Azure. There are three ways to do this: - If your company (or you) already has an account, then you are ready to go! - You can also open your own Azure account for free. _At time of writing_, you get $200 -of credit to spend on Azure services for 30 days. You can use this credit to try out paid Azure -services, exploring Microsoft's cloud for free. Even after the first 30 days, you never have to pay -anything unless you decide to transition to paid services with a Pay-As-You-Go Azure subscription. -This is a great way to try out Azure and cloud computing, and you can -[read more in their comprehensive FAQ][Azure-Free-Account-FAQ]. + of credit to spend on Azure services for 30 days. You can use this credit to try out paid Azure + services, exploring Microsoft's cloud for free. Even after the first 30 days, you never have to pay + anything unless you decide to transition to paid services with a Pay-As-You-Go Azure subscription. + This is a great way to try out Azure and cloud computing, and you can + [read more in their comprehensive FAQ][Azure-Free-Account-FAQ]. - If you have an MSDN subscription, you can activate your Azure subscriber benefits. Your MSDN -subscription gives you recurring Azure credits every month, so why not put those credits to use and -try out GitLab right now? + subscription gives you recurring Azure credits every month, so why not put those credits to use and + try out GitLab right now? ## Working with Azure @@ -216,10 +216,10 @@ Like all servers, our VM will be running many services. However, we want to open ports to enable public internet access to two services in particular: 1. **HTTP** (port 80) - opening port 80 will enable our VM to respond to HTTP requests, allowing -public access to the instance of GitLab running on our VM. + public access to the instance of GitLab running on our VM. 1. **SSH** (port 22) - opening port 22 will enable our VM to respond to SSH connection requests, -allowing public access (with authentication) to remote terminal sessions -_(you'll see why we need [SSH] access to our VM [later on in this tutorial](#maintaining-your-gitlab-instance))_ + allowing public access (with authentication) to remote terminal sessions + _(you'll see why we need [SSH] access to our VM [later on in this tutorial](#maintaining-your-gitlab-instance))_ ### Open HTTP on Port 80 diff --git a/doc/install/kubernetes/preparation/eks.md b/doc/install/kubernetes/preparation/eks.md index 647b856d54e..ea3b075dd82 100644 --- a/doc/install/kubernetes/preparation/eks.md +++ b/doc/install/kubernetes/preparation/eks.md @@ -5,6 +5,7 @@ There are a few nuances to Amazon EKS which are important to be aware of, when d ## Persistent volume management There are two methods to manage volume claims on Kubernetes: + 1. Manually creating each persistent volume (recommended on EKS) 1. Utilizing dynamic provisioning to automatically create the persistent volumes diff --git a/doc/install/kubernetes/preparation/tiller.md b/doc/install/kubernetes/preparation/tiller.md index 107df074b3b..00b0737402b 100644 --- a/doc/install/kubernetes/preparation/tiller.md +++ b/doc/install/kubernetes/preparation/tiller.md @@ -15,7 +15,7 @@ cannot be normally deployed. Tiller is deployed into the cluster and interacts with the Kubernetes API to deploy your applications. If role based access control (RBAC) is enabled, Tiller will need to be [granted permissions](#preparing-for-helm-with-rbac) to allow it to talk to the Kubernetes API. -If RBAC is not enabled, skip to [initalizing Helm](#initialize-helm). +If RBAC is not enabled, skip to [initializing Helm](#initialize-helm). If you are not sure whether RBAC is enabled in your cluster, or to learn more, read through our [RBAC documentation](rbac.md). @@ -54,19 +54,25 @@ Some clusters require authentication to use `kubectl` to create the Tiller roles #### Upload the RBAC config as an admin user (GKE) -For GKE, you need to grab the admin credentials: +For GKE, you need to obtain the admin credentials. This command will output the admin password: ``` gcloud container clusters describe <cluster-name> --zone <zone> --project <project-id> --format='value(masterAuth.password)' ``` -This command will output the admin password. We need the password to authenticate with `kubectl` and create the role. +Use the admin password to set the admin credentials. Replace the password value below with the output value from the above step: ``` -kubectl --username=admin --password=xxxxxxxxxxxxxx create -f rbac-config.yaml +kubectl config set-credentials admin --username=admin --password=xxxxxxxxxxxxxx ``` -#### Upload the RBAC config (Other clusters) +Once credentials have been set, create the role: + +``` +kubectl --user=admin create -f rbac-config.yaml +``` + +#### Upload the RBAC config (Non-GKE clusters) For other clusters like Amazon EKS, you can directly upload the RBAC configuration. diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 1b7e0d1d0ab..c1f2297f3be 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -33,7 +33,7 @@ Please consider using a virtual machine to run GitLab. ## Ruby versions -GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13. +GitLab requires Ruby (MRI) 2.5. Support for Ruby versions below 2.5 (2.3, 2.4) will stop with GitLab 11.6. You will have to use the standard MRI implementation of Ruby. We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab diff --git a/doc/integration/README.md b/doc/integration/README.md index 7941cb42667..f5bc0693b84 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -11,8 +11,7 @@ See the documentation below for details on how to configure these services. - [Akismet](akismet.md) Configure Akismet to stop spam - [Auth0 OmniAuth](auth0.md) Enable the Auth0 OmniAuth provider -- [Bitbucket](bitbucket.md) Import projects from Bitbucket.org and login to your GitLab instance with your -Bitbucket.org account +- [Bitbucket](bitbucket.md) Import projects from Bitbucket.org and login to your GitLab instance with your Bitbucket.org account - [CAS](cas.md) Configure GitLab to sign in using CAS - [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc. - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md index e2ed7a4b1ab..c67375ede50 100644 --- a/doc/integration/auth0.md +++ b/doc/integration/auth0.md @@ -4,18 +4,18 @@ To enable the Auth0 OmniAuth provider, you must create an Auth0 account, and an application. 1. Sign in to the [Auth0 Console](https://auth0.com/auth/login). If you need to -create an account, you can do so at the same link. + create an account, you can do so at the same link. 1. Select "New App/API". 1. Provide the Application Name ('GitLab' works fine). 1. Once created, you should see the Quick Start options. Disregard them and -select 'Settings' above the Quick Start options. + select 'Settings' above the Quick Start options. 1. At the top of the Settings screen, you should see your Domain, Client ID and -Client Secret. Take note of these as you'll need to put them in the -configuration file. For example: + Client Secret. Take note of these as you'll need to put them in the + configuration file. For example: - Domain: `test1234.auth0.com` - Client ID: `t6X8L2465bNePWLOvt9yi41i` - Client Secret: `KbveM3nqfjwCbrhaUy_gDu2dss8TIlHIdzlyf33pB7dEK5u_NyQdp65O_o02hXs2` @@ -44,7 +44,7 @@ configuration file. For example: ``` 1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) -for initial settings. + for initial settings. 1. Add the provider configuration: @@ -76,10 +76,10 @@ for initial settings. ``` 1. Change `YOUR_AUTH0_CLIENT_ID` to the client ID from the Auth0 Console page -from step 5. + from step 5. 1. Change `YOUR_AUTH0_CLIENT_SECRET` to the client secret from the Auth0 Console -page from step 5. + page from step 5. 1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you installed GitLab via Omnibus or from source respectively. diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md index 075feaeead9..edd1af423ca 100644 --- a/doc/integration/external-issue-tracker.md +++ b/doc/integration/external-issue-tracker.md @@ -1,8 +1,8 @@ # External issue tracker GitLab has a great issue tracker but you can also use an external one such as -Jira, Redmine, or Bugzilla. Issue trackers are configurable per GitLab project and allow -you to do the following: +Jira, Redmine, YouTrack, or Bugzilla. Issue trackers are configurable per GitLab project +and allow you to do the following: - you can reference these external issues inside GitLab interface (merge requests, commits, comments) and they will be automatically converted @@ -20,6 +20,7 @@ To enable an external issue tracker you must configure the appropriate **Service Visit the links below for details: - [Redmine](../user/project/integrations/redmine.md) +- [YouTrack](../user/project/integrations/youtrack.md) - [Jira](../user/project/integrations/jira.md) - [Bugzilla](../user/project/integrations/bugzilla.md) - [Custom Issue Tracker](../user/project/integrations/custom_issue_tracker.md) diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md index a67de23b17b..fe789a80eed 100644 --- a/doc/integration/facebook.md +++ b/doc/integration/facebook.md @@ -9,7 +9,7 @@ To enable the Facebook OmniAuth provider you must register your application with 1. Select the type "Website" 1. Enter a name for your app. This can be anything. Consider something like "<Organization>'s GitLab" or "<Your Name>'s GitLab" or -something else descriptive. + something else descriptive. 1. Choose "Create New Facebook App ID" diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md index 82ba1d7d984..cd755089be8 100644 --- a/doc/integration/slash_commands.md +++ b/doc/integration/slash_commands.md @@ -1,5 +1,7 @@ # Slash Commands +> The `run` command was [introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9. + Slash commands in Mattermost and Slack allow you to control GitLab and view GitLab content right inside your chat client, without having to leave it. For Slack, this requires a [project service configuration](../user/project/integrations/slack_slash_commands.md). Simply type the command as a message in your chat client to activate it. Commands are scoped to a project, with a trigger term that is specified during configuration. @@ -16,6 +18,7 @@ Taking the trigger term as `project-name`, the commands are: | `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` | | `/project-name issue move <id> to <project>` | Moves issue ID `<id>` to `<project>` | | `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment | +| `/project-name run <job name> <arguments>` | Execute [ChatOps](../ci/chatops/README.md) job `<job name>` on `master` | Note that if you are using the [GitLab Slack application](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html) for your GitLab.com projects, you need to [add the `gitlab` keyword at the beginning of the command](https://docs.gitlab.com/ee/user/project/integrations/gitlab_slack_application.html#usage). diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index 81ee7338e4e..8601551e3bd 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -1,6 +1,6 @@ # Public access -GitLab allows you to change your projects' visibility in order be accessed +GitLab allows [Owners](../user/permissions.md) to change a projects' visibility in order to be accessed **publicly** or **internally**. Projects with either of these visibility levels will be listed in the diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 2514ca94775..069c87f921a 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -442,6 +442,8 @@ backups will be copied to, and will be created if it does not exist. If the directory that you want to copy the tarballs to is the root of your mounted directory, just use `.` instead. +NOTE: **Note:** Since file system performance may affect GitLab's overall performance, we do not recommend using EFS for storage. See the [relevant documentation](../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details. + For Omnibus GitLab packages: 1. Edit `/etc/gitlab/gitlab.rb`: diff --git a/doc/security/img/ssh_keys_restricted_key_icon.png b/doc/security/img/ssh_keys_restricted_key_icon.png Binary files differnew file mode 100644 index 00000000000..ad3749e8233 --- /dev/null +++ b/doc/security/img/ssh_keys_restricted_key_icon.png diff --git a/doc/security/ssh_keys_restrictions.md b/doc/security/ssh_keys_restrictions.md index 213fa5bfef5..6b6a8a06cc9 100644 --- a/doc/security/ssh_keys_restrictions.md +++ b/doc/security/ssh_keys_restrictions.md @@ -17,3 +17,11 @@ In the Admin area under **Settings** (`/admin/application_settings`), look for the "Visibility and Access Controls" area: ![SSH keys restriction admin settings](img/ssh_keys_restrictions_settings.png) + +If a restriction is imposed on any key type, users will be unable to upload new SSH keys that don't meet the requirement. Any existing keys that don't meet it will be disabled but not removed and users will be unable to pull or push code using them. + +An icon will be visible to the user of a restricted key in the SSH keys section of their profile: + +![Restricted SSH key icon](img/ssh_keys_restricted_key_icon.png) + +Hovering over this icon will tell you why the key is restricted. diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index 55b678d6af5..a3698d60f6d 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -147,7 +147,7 @@ Please refer to `group_rename` and `user_rename` for that case. "user_name": "John Smith", "user_username": "johnsmith", "user_id": 41, - "project_visibility": "private" + "project_visibility": "visibilitylevel|private" } ``` @@ -158,7 +158,7 @@ Please refer to `group_rename` and `user_rename` for that case. "created_at": "2012-07-21T07:30:56Z", "updated_at": "2012-07-21T07:38:22Z", "event_name": "user_remove_from_team", - "project_access": "Maintainer", + "access_level": "Maintainer", "project_id": 74, "project_name": "StoreCloud", "project_path": "storecloud", @@ -167,7 +167,7 @@ Please refer to `group_rename` and `user_rename` for that case. "user_name": "John Smith", "user_username": "johnsmith", "user_id": 41, - "project_visibility": "private" + "project_visibility": "visibilitylevel|private" } ``` diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 04d3b5cb277..2e1df9d50d4 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -13,7 +13,7 @@ Starting with GitLab 11.3, the Auto DevOps pipeline is enabled by default for al projects. If it has not been explicitly enabled for the project, Auto DevOps will be automatically disabled on the first pipeline failure. Your project will continue to use an alternative [CI/CD configuration file](../../ci/yaml/README.md) if one is found. A GitLab -administrator can [change this setting](../../user/admin_area/settings/continuous_integration.html#auto-devops) +administrator can [change this setting](../../user/admin_area/settings/continuous_integration.html#auto-devops-core-only) in the admin area. With Auto DevOps, the software development process becomes easier to set up @@ -114,7 +114,7 @@ To make full use of Auto DevOps, you will need: will need Prometheus installed somewhere (inside or outside your cluster) and configured to scrape your Kubernetes cluster. To get response metrics (in addition to system metrics), you need to - [configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-prometheus-to-monitor-for-nginx-ingress-metrics). + [configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-nginx-ingress-monitoring). The [Prometheus service](../../user/project/integrations/prometheus.md) integration needs to be enabled for the project, or enabled as a [default service template](../../user/project/integrations/services_templates.md) @@ -166,7 +166,7 @@ them to the Kubernetes pods that run your application(s). When using Auto DevOps, you may want to deploy different environments to different Kubernetes clusters. This is possible due to the 1:1 connection that -[exists between them](../../user/project/clusters/index.md#multiple-kubernetes-clusters). +[exists between them](../../user/project/clusters/index.md#multiple-kubernetes-clusters-premium). In the [Auto DevOps template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml) (used behind the scenes by Auto DevOps), there are currently 3 defined environment names that you need to know: @@ -179,7 +179,7 @@ Those environments are tied to jobs that use [Auto Deploy](#auto-deploy), so except for the environment scope, they would also need to have a different domain they would be deployed to. This is why you need to define a separate `KUBE_INGRESS_BASE_DOMAIN` variable for all the above -[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-variables). +[based on the environment](https://docs.gitlab.com/ee/ci/variables/index.html#limiting-environment-scopes-of-variables-premium). The following table is an example of how the three different clusters would be configured. @@ -188,7 +188,7 @@ be configured. | ------------ | -------------- | ----------------------------- | ------------- | ------ | | review | `review/*` | `review.example.com` | `review/*` | The review cluster which will run all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, which means it will be used by every environment name starting with `review/`. | | staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which will run the deployments of the staging environments. You need to [enable it first](#deploy-policy-for-staging-and-production-environments). | -| production | `production` | `example.com` | `production` | The production cluster which will run the deployments of the production environment. You can use [incremental rollouts](#incremental-rollout-to-production). | +| production | `production` | `example.com` | `production` | The production cluster which will run the deployments of the production environment. You can use [incremental rollouts](#incremental-rollout-to-production-premium). | To add a different cluster for each environment: @@ -269,12 +269,12 @@ The available options are: - **Continuous deployment to production**: Enables [Auto Deploy](#auto-deploy) with `master` branch directly deployed to production. - **Continuous deployment to production using timed incremental rollout**: Sets the - [`INCREMENTAL_ROLLOUT_MODE`](#timed-incremental-rollout-to-production) variable + [`INCREMENTAL_ROLLOUT_MODE`](#timed-incremental-rollout-to-production-premium) variable to `timed`, and production deployment will be executed with a 5 minute delay between each increment in rollout. - **Automatic deployment to staging, manual deployment to production**: Sets the [`STAGING_ENABLED`](#deploy-policy-for-staging-and-production-environments) and - [`INCREMENTAL_ROLLOUT_MODE`](#incremental-rollout-to-production) variables + [`INCREMENTAL_ROLLOUT_MODE`](#incremental-rollout-to-production-premium) variables to `1` and `manual`. This means: - `master` branch is directly deployed to staging. @@ -503,7 +503,7 @@ Auto Deploy doesn't include deployments to staging or canary by default, but the [Auto DevOps template] contains job definitions for these tasks if you want to enable them. -You can make use of [environment variables](#helm-chart-variables) to automatically +You can make use of [environment variables](#environment-variables) to automatically scale your pod replicas. It's important to note that when a project is deployed to a Kubernetes cluster, @@ -581,7 +581,7 @@ The metrics include: In order to make use of monitoring you need to: -1. [Deploy Prometheus](../../user/project/integrations/prometheus.md#configuring-your-own-prometheus-server-within-kubernetes) into your Kubernetes cluster +1. [Deploy Prometheus](../../user/project/integrations/prometheus.md) into your Kubernetes cluster 1. If you would like response metrics, ensure you are running at least version 0.9.0 of NGINX Ingress and [enable Prometheus metrics](https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/customization/custom-vts-metrics-prometheus/nginx-vts-metrics-conf.yaml). @@ -684,7 +684,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | **Variable** | **Description** | | ------------ | --------------- | -| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain). By default, set automatically by the [Auto DevOps setting](#enabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959). Use `KUBE_INGRESS_BASE_DOMAIN` instead. | +| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-base-domain). By default, set automatically by the [Auto DevOps setting](#enablingdisabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959). Use `KUBE_INGRESS_BASE_DOMAIN` instead. | | `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/auto-deploy-app). | | `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. | | `REPLICAS` | The number of replicas to deploy; defaults to 1. | @@ -697,14 +697,15 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. | | `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. | | `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-environment-variables). Set it to use a custom database name. | +| `POSTGRES_VERSION` | Tag for the [`postgres` Docker image](https://hub.docker.com/_/postgres) to use. Defaults to `9.6.2`. | | `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` | | `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`.| | `DEP_SCAN_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).| | `DB_INITIALIZE` | From GitLab 11.4, this variable can be used to specify the command to run to initialize the application's PostgreSQL database. It runs inside the application pod. | | `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. | | `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | -| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). | -| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> | +| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). | +| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> | | `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. | | `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. | | `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. | @@ -938,7 +939,7 @@ TIP: **Tip:** You can also set this inside your [project's settings](#deployment-strategy). This configuration based on -[incremental rollout to production](#incremental-rollout-to-production). +[incremental rollout to production](#incremental-rollout-to-production-premium). Everything behaves the same way, except: diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md index 9749bd63f2b..367e192b85a 100644 --- a/doc/topics/autodevops/quick_start_guide.md +++ b/doc/topics/autodevops/quick_start_guide.md @@ -159,15 +159,15 @@ In the **test** stage, GitLab runs various checks on the application: - The `test` job runs unit and integration tests by detecting the language and framework ([Auto Test](index.md#auto-test)) - The `code_quality` job checks the code quality and is allowed to fail - ([Auto Code Quality](index.md#auto-code-quality)) **[STARTER]** + ([Auto Code Quality](index.md#auto-code-quality-starter)) **[STARTER]** - The `container_scanning` job checks the Docker container if it has any vulnerabilities and is allowed to fail ([Auto Container Scanning](index.md#auto-container-scanning)) - The `dependency_scanning` job checks if the application has any dependencies - susceptible to vulnerabilities and is allowed to fail ([Auto Dependency Scanning](index.md#auto-dependency-scanning)) **[ULTIMATE]** + susceptible to vulnerabilities and is allowed to fail ([Auto Dependency Scanning](index.md#auto-dependency-scanning-ultimate)) **[ULTIMATE]** - The `sast` job runs static analysis on the current code to check for potential - security issues and is allowed to fail([Auto SAST](index.md#auto-sast)) **[ULTIMATE]** + security issues and is allowed to fail([Auto SAST](index.md#auto-sast-ultimate)) **[ULTIMATE]** - The `license_management` job searches the application's dependencies to determine each of their - licenses and is allowed to fail ([Auto License Management](index.md#auto-license-management)) **[ULTIMATE]** + licenses and is allowed to fail ([Auto License Management](index.md#auto-license-management-ultimate)) **[ULTIMATE]** NOTE: **Note:** As you might have noticed, all jobs except `test` are allowed to fail in the @@ -178,7 +178,7 @@ deploys the application in Kubernetes ([Auto Deploy](index.md#auto-deploy)). Lastly, in the **performance** stage, some performance tests will run on the deployed application -([Auto Browser Performance Testing](index.md#auto-browser-performance-testing)). **[PREMIUM]** +([Auto Browser Performance Testing](index.md#auto-browser-performance-testing-premium)). **[PREMIUM]** --- @@ -285,8 +285,8 @@ all within GitLab. Despite its automatic nature, Auto DevOps can also be configu and customized to fit your workflow. Here are some helpful resources for further reading: 1. [Auto DevOps](index.md) -1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters) **[PREMIUM]** -1. [Incremental rollout to production](index.md#incremental-rollout-to-production) **[PREMIUM]** +1. [Multiple Kubernetes clusters](index.md#using-multiple-kubernetes-clusters-premium) **[PREMIUM]** +1. [Incremental rollout to production](index.md#incremental-rollout-to-production-premium) **[PREMIUM]** 1. [Disable jobs you don't need with environment variables](index.md#environment-variables) 1. [Use a static IP for your cluster](../../user/project/clusters/index.md#using-a-static-ip) 1. [Use your own buildpacks to build your application](index.md#custom-buildpacks) diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md index b4ba5b7a24e..1906655dfa5 100644 --- a/doc/university/high-availability/aws/README.md +++ b/doc/university/high-availability/aws/README.md @@ -3,7 +3,7 @@ comments: false --- > **Note**: We **do not** recommend using the AWS Elastic File System (EFS), as it can result -in [significantly degraded performance](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/administration/high_availability/nfs.md#aws-elastic-file-system). +in [significantly degraded performance](../../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs). # High Availability on AWS @@ -125,10 +125,10 @@ image below we have the settings for this article but note the following two options which are of particular interest for HA: 1. Multi-AZ-Deployment is recommended as redundancy. Read more at -[High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html) + [High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html) 1. While we chose a General Purpose (SSD) for this article a Provisioned -IOPS (SSD) is best suited for HA. Read more about it at -[Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html) + IOPS (SSD) is best suited for HA. Read more about it at + [Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html) ![RDS Instance Specs](img/instance_specs.png) @@ -182,7 +182,7 @@ Another option is to build a simple NFS server using a vanilla Linux server back by AWS Elastic Block Storage (EBS). > **Note:** GitLab does not recommend using AWS Elastic File System (EFS). See - details in [High Availability NFS documentation](../../../administration/high_availability/nfs.md#aws-elastic-file-system) + details in [High Availability NFS documentation](../../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) *** diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md index 60c0eadc572..99fb5e83387 100644 --- a/doc/university/training/end-user/README.md +++ b/doc/university/training/end-user/README.md @@ -86,6 +86,7 @@ git config --global user.email you@example.com ```bash git config --global --list ``` + - You might want or be required to use an SSH key. - Instructions: [SSH](http://doc.gitlab.com/ce/ssh/README.html) @@ -413,6 +414,7 @@ Revert is safer considering we can revert a revert --- ### Version Control + - Local VCS was used with a filesystem or a simple db. - Centralized VCS such as Subversion includes collaboration but still is prone to data loss as the main server is the single point of diff --git a/doc/university/training/topics/stash.md b/doc/university/training/topics/stash.md index f1c91fb1b37..dfd28fbcbc9 100644 --- a/doc/university/training/topics/stash.md +++ b/doc/university/training/topics/stash.md @@ -30,7 +30,7 @@ and we need to change to a different branch. ---------- - Every time we save a stash it gets stacked so by using list we can see all our -stashes. + stashes. ``` git stash list diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 1f4b5fbffa7..350072186ee 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -95,7 +95,7 @@ Now, you can use pgloader to migrate the data from MySQL to PostgreSQL: ``` 1. Once the migration finishes, you should see a summary table that looks like -the following: + the following: ``` table name read imported errors total time @@ -242,7 +242,7 @@ Now, you can use pgloader to migrate the data from MySQL to PostgreSQL: ``` 1. Once the migration finishes, you should see a summary table that looks like -the following: + the following: ``` table name read imported errors total time diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md new file mode 100644 index 00000000000..00cea22e4e1 --- /dev/null +++ b/doc/user/admin_area/index.md @@ -0,0 +1,29 @@ +# GitLab Admin Area **[CORE ONLY]** + +The Admin Area provides a web UI for administering some features of GitLab self-managed instances. + +To access the Admin Area, either: + +- Click the Admin Area icon (the spanner or wrench icon). +- Visit `/admin` on your self-managed instance. + +NOTE: **Note:** +Only admin users can access the Admin Area. + +## Admin Area sections + +The Admin Area is made up of the following sections: + +| Section | Description | +|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------| +| Overview | View your GitLab Dashboard, and maintain projects, users, groups, jobs, runners, and Gitaly servers. | +| Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. | +| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. | +| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. | +| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. | +| Abuse Reports | Manage [abuse reports](abuse_reports.md) submitted by your users. | +| Deploy Keys | Create instance-wide [SSH deploy keys](../../ssh/README.md#deploy-keys). | +| Service Templates | Create [service templates](../project/integrations/services_templates.md) for projects. | +| Labels | Create and maintain [labels](labels.md) for your GitLab instance. | +| Appearance | Customize [GitLab's appearance](../../customization/index.md). | +| Settings | Modify the [settings](settings/index.md) for your GitLab instance. | diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index d4853a5842e..01979f12a01 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -20,7 +20,7 @@ From now on, every existing project and newly created ones that don't have a `.gitlab-ci.yml`, will use the Auto DevOps pipelines. If you want to disable it for a specific project, you can do so in -[its settings](../../../topics/autodevops/index.md#enabling-auto-devops). +[its settings](../../../topics/autodevops/index.md##enablingdisabling-auto-devops). ## Maximum artifacts size **[CORE ONLY]** @@ -38,7 +38,7 @@ To change it: The default expiration time of the [job artifacts](../../../administration/job_artifacts.md) can be set in the Admin area of your GitLab instance. The syntax of duration is -described in [`artifacts:expire_in`](../../../ci/yaml/README.md#artifacts-expire_in) +described in [`artifacts:expire_in`](../../../ci/yaml/README.md#artifactsexpire_in) and the default value is `30 days`. On GitLab.com they [never expire](../../gitlab_com/index.md#gitlab-ci-cd). @@ -47,7 +47,7 @@ and the default value is `30 days`. On GitLab.com they 1. Hit **Save changes** for the changes to take effect. This setting is set per job and can be overridden in -[`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifacts-expire_in). +[`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifactsexpire_in). To disable the expiration, set it to `0`. The default unit is in seconds. ## Archive jobs **[CORE ONLY]** diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index d202b1260ad..1e2fad1efe9 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -248,10 +248,10 @@ For large projects with many contributors, it may be useful to stop discussions in issues or merge requests in these scenarios: - The project maintainer has already resolved the discussion and it is not helpful -for continued feedback. The project maintainer has already directed new conversation -to newer issues or merge requests. + for continued feedback. The project maintainer has already directed new conversation + to newer issues or merge requests. - The people participating in the discussion are trolling, abusive, or otherwise -being unproductive. + being unproductive. In these cases, a user with Developer permissions or higher in the project can lock (and unlock) an issue or a merge request, using the "Lock" section in the sidebar. For issues, @@ -276,17 +276,17 @@ Additionally locked issues can not be reopened. ## Filtering notes -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26723) in GitLab 11.5. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26723) in GitLab 11.5. -For issues with many comments like activity notes and user comments, sometimes -finding useful information can be hard. There is a way to filter comments from single notes and discussions for merge requests and issues. +For issues with many comments like activity notes and user comments, sometimes +finding useful information can be hard. There is a way to filter comments from single notes and discussions for merge requests and issues. From a merge request's **Discussion** tab, or from an epic/issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options: - **Show all activity**: displays all user comments and system notes -(issue updates, mentions from other issues, changes to the description, etc). + (issue updates, mentions from other issues, changes to the description, etc). - **Show comments only**: only displays user comments in the list. -- **Show history only**: only displays activity notes. +- **Show history only**: only displays activity notes. ![Notes filters dropdown options](img/index_notes_filters.png) @@ -298,21 +298,21 @@ from any device you're logged into. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6. -As a reviewer, you're able to suggest code changes with a simple +As a reviewer, you're able to suggest code changes with a simple markdown syntax in Merge Request Diff discussions. Then, the -Merge Request author (or other users with appropriate +Merge Request author (or other users with appropriate [permission](../permissions.md)) is able to apply these -suggestions with a click, which will generate a commit in +suggestions with a click, which will generate a commit in the Merge Request authored by the user that applied them. 1. Choose a line of code to be changed, add a new comment, then click -on the **Insert suggestion** icon in the toolbar: + on the **Insert suggestion** icon in the toolbar: ![Add a new comment](img/insert_suggestion.png) - + > **Note:** The suggestion will only affect the commented line. Multi-line - suggestions are currently not supported. Will be introduced by + suggestions are currently not supported. Will be introduced by [#53310](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310). 1. In the comment, add your suggestion to the pre-populated code block: @@ -327,9 +327,9 @@ on the **Insert suggestion** icon in the toolbar: ![Apply suggestions](img/suggestion.png) > **Note:** - Discussions are _not_ automatically resolved. Will be introduced by + Discussions are _not_ automatically resolved. Will be introduced by [#54405](https://gitlab.com/gitlab-org/gitlab-ce/issues/54405). - + Once the author applies a suggestion, it will be marked with the **Applied** label, and GitLab will create a new commit with the message `Apply suggestion to <file-name>` and push the suggested change directly into the codebase in the merge request's branch. diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index c1c9b8bf43c..762cf911fcf 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -60,7 +60,7 @@ Below are the current settings regarding [GitLab CI/CD](../../ci/README.md). | Setting | GitLab.com | Default | | ----------- | ----------------- | ------------- | | Artifacts maximum size | 1G | 100M | -| Artifacts [expiry time](../../ci/yaml/README.md#artifacts-expire_in) | kept forever | deleted after 30 days unless otherwise specified | +| Artifacts [expiry time](../../ci/yaml/README.md#artifactsexpire_in) | kept forever | deleted after 30 days unless otherwise specified | ## Repository size limit diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 5fea683a7fd..300e0115c60 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -18,9 +18,9 @@ a link to the group settings. By clicking the last button you can leave that gro You can create groups for numerous reasons. To name a few: - Organize related projects under the same [namespace](#namespaces), add members to that -group and grant access to all their projects at once + group and grant access to all their projects at once - Create a group, include members of your team, and make it easier to -`@mention` all the team at once in issues and merge requests + `@mention` all the team at once in issues and merge requests - Create a group for your company members, and create [subgroups](subgroups/index.md) for each individual team. Let's say you create a group called `company-team`, and among others, you created subgroups in this group for each individual team `backend-team`, @@ -43,11 +43,11 @@ In GitLab, a namespace is a unique name to be used as a user name, a group name, For example, consider a user named Alex: 1. Alex creates an account on GitLab.com with the username `alex`; -their profile will be accessed under `https://gitlab.example.com/alex` + their profile will be accessed under `https://gitlab.example.com/alex` 1. Alex creates a group for their team with the groupname `alex-team`; -the group and its projects will be accessed under `https://gitlab.example.com/alex-team` + the group and its projects will be accessed under `https://gitlab.example.com/alex-team` 1. Alex creates a subgroup of `alex-team` with the subgroup name `marketing`; -this subgroup and its projects will be accessed under `https://gitlab.example.com/alex-team/marketing` + this subgroup and its projects will be accessed under `https://gitlab.example.com/alex-team/marketing` By doing so: @@ -107,7 +107,7 @@ Consider we have a group with two projects: - On the **Group Members** page we can now add a new user to the group. - Now because this user is a **Developer** member of the group, he automatically -gets **Developer** access to **all projects** within that group. + gets **Developer** access to **all projects** within that group. If necessary, you can increase the access level of an individual user for a specific project, by adding them again as a new member to the project with the new permission levels. @@ -267,9 +267,9 @@ Define project templates at a group-level by setting a group as a template sourc ### Advanced settings - **Projects**: view all projects within that group, add members to each project, -access each project's settings, and remove any project from the same screen. + access each project's settings, and remove any project from the same screen. - **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) to your group. - **Kubernetes cluster integration**: connect your GitLab group with [Kubernetes clusters](clusters/index.md). - **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events) -for the group. **[STARTER ONLY]** + for the group. **[STARTER ONLY]** - **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group. diff --git a/doc/user/index.md b/doc/user/index.md index 22506b30498..71378920ed4 100644 --- a/doc/user/index.md +++ b/doc/user/index.md @@ -38,10 +38,10 @@ GitLab is a Git-based platform that integrates a great number of essential tools - Hosting code in repositories with version control - Tracking proposals for new implementations, bug reports, and feedback with a -fully featured [Issue Tracker](project/issues/index.md#issue-tracker) + fully featured [Issue Tracker](project/issues/index.md#issue-tracker) - Organizing and prioritizing with [Issue Boards](project/issues/index.md#issue-boards) - Reviewing code in [Merge Requests](project/merge_requests/index.md) with live-preview changes per -branch with [Review Apps](../ci/review_apps/index.md) + branch with [Review Apps](../ci/review_apps/index.md) - Building, testing and deploying with built-in [Continuous Integration](../ci/README.md) - Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md) - Integrating with Docker by using [GitLab Container Registry](project/container_registry.md) @@ -51,9 +51,9 @@ With GitLab Enterprise Edition, you can also: - Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html) - Improve collaboration with -[Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals), -[Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html), -and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) + [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals), + [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html), + and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) - Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html) - Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software. - Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) and [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for faster, more advanced code search across your entire GitLab instance @@ -77,12 +77,12 @@ build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do it all at once, from one single project. - [Repositories](project/repository/index.md): Host your codebase in -repositories with version control and as part of a fully integrated platform. + repositories with version control and as part of a fully integrated platform. - [Issues](project/issues/index.md): Explore the best of GitLab Issues' features. - [Merge Requests](project/merge_requests/index.md): Collaborate on code, -reviews, live preview changes per branch, and request approvals with Merge Requests. + reviews, live preview changes per branch, and request approvals with Merge Requests. - [Milestones](project/milestones/index.md): Work on multiple issues and merge -requests towards the same target date with Milestones. + requests towards the same target date with Milestones. ## GitLab CI/CD @@ -92,9 +92,9 @@ directly from GitLab. No third-party integrations needed. - [GitLab Auto Deploy](../ci/autodeploy/index.md): Deploy your application out-of-the-box with GitLab Auto Deploy. - [Review Apps](../ci/review_apps/index.md): Live-preview the changes introduced by a merge request with Review Apps. - [GitLab Pages](project/pages/index.md): Publish your static site directly from -GitLab with GitLab Pages. You can build, test, and deploy any Static Site Generator with Pages. + GitLab with GitLab Pages. You can build, test, and deploy any Static Site Generator with Pages. - [GitLab Container Registry](project/container_registry.md): Build and deploy Docker -images with Container Registry. + images with Container Registry. ## Account @@ -102,13 +102,13 @@ There is a lot you can customize and configure to enjoy the best of GitLab. - [Settings](profile/index.md): Manage your user settings to change your personal info, -personal access tokens, authorized applications, etc. + personal access tokens, authorized applications, etc. - [Authentication](../topics/authentication/index.md): Read through the authentication -methods available in GitLab. + methods available in GitLab. - [Permissions](permissions.md): Learn the different set of permissions levels for each -user type (guest, reporter, developer, maintainer, owner). + user type (guest, reporter, developer, maintainer, owner). - [Feature highlight](feature_highlight.md): Learn more about the little blue dots -around the app that explain certain features + around the app that explain certain features - [Abuse reports](abuse_reports.md): Report abuse from users to GitLab administrators ## Groups diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 74a966f3a17..dff77acd71b 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -97,7 +97,7 @@ The following table depicts the various user permission levels in a project. | Manage variables | | | | ✓ | ✓ | | Manage GitLab Pages | | | | ✓ | ✓ | | Manage GitLab Pages domains and certificates | | | | ✓ | ✓ | -| Remove GitLab Pages | | | | | ✓ | +| Remove GitLab Pages | | | | ✓ | ✓ | | View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ | | Manage clusters | | | | ✓ | ✓ | | Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ | @@ -107,7 +107,6 @@ The following table depicts the various user permission levels in a project. | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | | Delete issues | | | | | ✓ | -| Remove pages | | | | | ✓ | | Force push to protected branches [^4] | | | | | | | Remove protected branches [^4] | | | | | | | View project Audit Events | | | | ✓ | ✓ | diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index 0a7c5832a2d..8e5fe4b0fb9 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -41,7 +41,7 @@ or a U2F device. **On your phone:** 1. Install a compatible application. We recommend [Google Authenticator] -\(proprietary\) or [FreeOTP] \(open source\). + \(proprietary\) or [FreeOTP] \(open source\). 1. In the application, add a new entry in one of two ways: - Scan the code with your phone's camera to add the entry automatically. - Enter the details provided to add the entry manually. diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index ba756c4b793..a2b15d058d7 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -35,13 +35,13 @@ From there, you can: - Manage [2FA](account/two_factor_authentication.md) - Change your username and [delete your account](account/delete_account.md) - Manage applications that can -[use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth) + [use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth) - Manage [personal access tokens](personal_access_tokens.md) to access your account via API and authorized applications - Add and delete emails linked to your account - Choose which email to use for notifications, web-based commits, and display on your public profile - Manage [SSH keys](../../ssh/README.md#ssh) to access your account via SSH - Manage your [preferences](preferences.md#syntax-highlighting-theme) -to customize your own GitLab experience + to customize your own GitLab experience - [View your active sessions](active_sessions.md) and revoke any of them if necessary - Access your audit log, a security log of important events involving your account diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md index 7387d1810ca..db68510c46d 100644 --- a/doc/user/profile/preferences.md +++ b/doc/user/profile/preferences.md @@ -93,6 +93,12 @@ You can choose between 3 options: ## Localization +### Language + +Select your preferred language from a list of supported languages. + +*This feature is experimental and translations are not complete yet.* + ### First day of the week The first day of the week can be customised for calendar views and date pickers. diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 85a4af24dc5..e601d7b2ccc 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -86,15 +86,20 @@ To add an existing Kubernetes cluster to your project: 1. Click **Add Kubernetes cluster**. 1. Click **Add an existing Kubernetes cluster** and fill in the details: - **Kubernetes cluster name** (required) - The name you wish to give the cluster. - - **Environment scope** (required)- The + - **Environment scope** (required) - The [associated environment](#setting-the-environment-scope) to this cluster. - **API URL** (required) - It's the URL that GitLab uses to access the Kubernetes API. Kubernetes exposes several APIs, we want the "base" URL that is common to all of them, e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`. - - **CA certificate** (optional) - - If the API is using a self-signed TLS certificate, you'll also need to include - the `ca.crt` contents here. + - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the EKS cluster. We will use the certificate created by default. + - List the secrets with `kubectl get secrets`, and one should named similar to + `default-token-xxxxx`. Copy that token name for use below. + - Get the certificate by running this command: + + ```sh + kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode + ``` - **Token** - GitLab authenticates against Kubernetes using service tokens, which are scoped to a particular `namespace`. @@ -102,36 +107,81 @@ To add an existing Kubernetes cluster to your project: [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) privileges.** To create this service account: - 1. Create a `gitlab` service account in the `default` namespace: + 1. Create a file called `gitlab-admin-service-account.yaml` with contents: - ```bash - kubectl create -f - <<EOF - apiVersion: v1 - kind: ServiceAccount - metadata: - name: gitlab - namespace: default - EOF + ```yaml + apiVersion: v1 + kind: ServiceAccount + metadata: + name: gitlab-admin + namespace: kube-system ``` - 1. Create a cluster role binding to give the `gitlab` service account - `cluster-admin` privileges: + + 2. Apply the service account to your cluster: ```bash - kubectl create -f - <<EOF + kubectl apply -f gitlab-admin-service-account.yaml + ``` + + Output: + + ```bash + serviceaccount "gitlab-admin" created + ``` + + 3. Create a file called `gitlab-admin-cluster-role-binding.yaml` with contents: + + ```yaml + apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding - apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: gitlab-cluster-admin - subjects: - - kind: ServiceAccount - name: gitlab - namespace: default + name: gitlab-admin roleRef: + apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin - apiGroup: rbac.authorization.k8s.io - EOF + subjects: + - kind: ServiceAccount + name: gitlab-admin + namespace: kube-system + ``` + + 4. Apply the cluster role binding to your cluster: + + ```bash + kubectl apply -f gitlab-admin-cluster-role-binding.yaml + ``` + + Output: + + ```bash + clusterrolebinding "gitlab-admin" created + ``` + + 5. Retrieve the token for the `gitlab-admin` service account: + + ```bash + kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}') ``` + + Copy the `<authentication_token>` value from the output: + + ```yaml + Name: gitlab-admin-token-b5zv4 + Namespace: kube-system + Labels: <none> + Annotations: kubernetes.io/service-account.name=gitlab-admin + kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8 + + Type: kubernetes.io/service-account-token + + Data + ==== + ca.crt: 1025 bytes + namespace: 11 bytes + token: <authentication_token> + ``` + NOTE: **Note:** For GKE clusters, you will need the `container.clusterRoleBindings.create` permission to create a cluster @@ -307,6 +357,28 @@ you should be careful as GitLab cannot detect it. In this case, installing Tiller via the applications will result in the cluster having it twice, which can lead to confusion during deployments. +### Upgrading applications + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789) +in GitLab 11.8. + +Users can perform a one-click upgrade for the GitLab Runner application, +when there is an upgrade available. + +To upgrade the GitLab Runner application: + +1. Navigate to your project's **Operations > Kubernetes**. +1. Select your cluster. +1. Click the **Upgrade** button for the Runnner application. + +The **Upgrade** button will not be shown if there is no upgrade +available. + +NOTE: **Note:** +Upgrades will reset values back to the values built into the `runner` +chart plus the values set by +[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml) + ## Getting the external IP address NOTE: **Note:** diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md index e1b8dc07b50..512a4cb32f4 100644 --- a/doc/user/project/clusters/runbooks/index.md +++ b/doc/user/project/clusters/runbooks/index.md @@ -23,7 +23,7 @@ runbooks. A sample runbook is provided, showcasing common operations. **<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch this [video](https://www.youtube.com/watch?v=Q_OqHIIUPjE) -for an overview of how this is acomplished in GitLab!** +for an overview of how this is accomplished in GitLab!** ## Requirements diff --git a/doc/user/project/clusters/serverless/img/app-domain.png b/doc/user/project/clusters/serverless/img/app-domain.png Binary files differdeleted file mode 100644 index d113dfadd2e..00000000000 --- a/doc/user/project/clusters/serverless/img/app-domain.png +++ /dev/null diff --git a/doc/user/project/clusters/serverless/img/dns-entry.png b/doc/user/project/clusters/serverless/img/dns-entry.png Binary files differindex 678743f256e..351e40b77d5 100644 --- a/doc/user/project/clusters/serverless/img/dns-entry.png +++ b/doc/user/project/clusters/serverless/img/dns-entry.png diff --git a/doc/user/project/clusters/serverless/img/install-knative.png b/doc/user/project/clusters/serverless/img/install-knative.png Binary files differindex 93b1cbe602f..ecc2f8fb481 100644 --- a/doc/user/project/clusters/serverless/img/install-knative.png +++ b/doc/user/project/clusters/serverless/img/install-knative.png diff --git a/doc/user/project/clusters/serverless/img/serverless-page.png b/doc/user/project/clusters/serverless/img/serverless-page.png Binary files differindex 814b8532205..a872fda7740 100644 --- a/doc/user/project/clusters/serverless/img/serverless-page.png +++ b/doc/user/project/clusters/serverless/img/serverless-page.png diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md index fc3a4a757d0..f7e72efa2ba 100644 --- a/doc/user/project/clusters/serverless/index.md +++ b/doc/user/project/clusters/serverless/index.md @@ -25,8 +25,12 @@ To run Knative on Gitlab, you will need: 1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative. The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab). + The set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory. 1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install Knative. +1. **GitLab Runner:** A runner is required to run the CI jobs that will deploy serverless + applications or functions onto your cluster. You can install the GitLab Runner + onto the existing Kubernetes cluster. See [Installing Applications](../index.md#installing-applications) for more information. 1. **Domain Name:** Knative will provide its own load balancer using Istio. It will provide an external IP address for all the applications served by Knative. You will be prompted to enter a wildcard domain where your applications will be served. Configure your DNS server to use the @@ -45,9 +49,9 @@ To run Knative on Gitlab, you will need: NOTE: **Note:** The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.50 GB memory. **RBAC must be enabled.** -1. [Add a Kubernetes cluster](../index.md) and install Helm. -1. Once Helm has been successfully installed, on the Knative app section, enter the domain to be used with - your application and click "Install". +1. [Add a Kubernetes cluster](../index.md) and [install Helm](../index.md#installing-applications). +1. Once Helm has been successfully installed, scroll down to the Knative app section. Enter the domain to be used with + your application/functions (e.g. `example.com`) and click **Install**. ![install-knative](img/install-knative.png) @@ -66,12 +70,16 @@ The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22. 1. The ingress is now available at this address and will route incoming requests to the proper service based on the DNS name in the request. To support this, a wildcard DNS A record should be created for the desired domain name. For example, - if your Knative base domain is `knative.info` then you need to create an A record with domain `*.knative.info` + if your Knative base domain is `example.com` then you need to create an A record with domain `*.example.com` pointing the ip address of the ingress. ![dns entry](img/dns-entry.png) -## Deploying Functions +NOTE: **Note:** +You can deploy either [functions](#deploying-functions) or [serverless applications](#deploying-serverless-applications) +on a given project but not both. The current implementation makes use of a `serverless.yml` file to signal a FaaS project. + +## Deploying functions > Introduced in GitLab 11.6. @@ -84,7 +92,7 @@ Currently the following [runtimes](https://gitlab.com/triggermesh/runtimes) are - node.js - kaniko -You can find all the files referenced in this doc in the [functions example project](https://gitlab.com/knative-examples/functions). +You can find and import all the files referenced in this doc in the **[functions example project](https://gitlab.com/knative-examples/functions)**. Follow these steps to deploy a function using the Node.js runtime to your Knative instance: @@ -188,16 +196,27 @@ The function details can be retrieved directly from Knative on the cluster: kubectl -n "$KUBE_NAMESPACE" get services.serving.knative.dev ``` -The sample function can now be triggered from any HTTP client using a simple `POST` call. +The sample function can now be triggered from any HTTP client using a simple `POST` call: + + 1. Using curl -![function exection](img/function-execution.png) + ```bash + curl \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"GitLab":"FaaS"}' \ + http://functions-echo.functions-1.functions.example.net/ + ``` + 2. Using a web-based tool (ie. postman, restlet, etc) + + ![function execution](img/function-execution.png) ## Deploying Serverless applications > Introduced in GitLab 11.5. NOTE: **Note:** -You can reference the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started. +You can reference and import the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started. Add the following `.gitlab-ci.yml` to the root of your repository (you may skip this step if using the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned above): @@ -236,11 +255,7 @@ With all the pieces in place, the next time a CI pipeline runs, the Knative appl ### Obtain the URL for the Knative deployment -Go to the **Operations > Serverless** page to find the URL for your deployment in the **Domain** column. - -![app domain](img/app-domain.png) - -Alternatively, use the CI/CD deployment job output to obtain the deployment URL. Once all the stages of the pipeline finish, click the **deploy** stage. +Go to the **CI/CD > Pipelines** and click on the pipeline that deployed your app. Once all the stages of the pipeline finish, click the **deploy** stage. ![deploy stage](img/deploy-stage.png) @@ -262,7 +277,7 @@ registry.staging.gitlab.com/danielgruesso/knative $ tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait Deployment started. Run "tm -n knative-4342902 describe service knative" to see the details Waiting for ready state....... -Service domain: knative.knative-4342902.knative.info +Service domain: knative.knative-4342902.example.com Job succeeded ``` diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index 529a82ded9e..05127b274e0 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -80,6 +80,7 @@ Here's a little explanation of how this works behind the scenes: To sum up, anything that doesn't follow the [GitLab flow] won't be tracked at all. So, the Cycle Analytics dashboard won't present any data: + - For merge requests that do not close an issue. - For issues not labeled with a label present in the Issue Board. - For issues not assigned a milestone. diff --git a/doc/user/project/import/bitbucket_server.md b/doc/user/project/import/bitbucket_server.md index ebf87890cfd..d51a0c0ccca 100644 --- a/doc/user/project/import/bitbucket_server.md +++ b/doc/user/project/import/bitbucket_server.md @@ -22,14 +22,14 @@ Import your projects from Bitbucket Server to GitLab with minimal effort. ## Limitations 1. Currently GitLab doesn't allow comments on arbitrary lines of code, so any -Bitbucket comments out of bounds will be inserted as comments in the merge -request. + Bitbucket comments out of bounds will be inserted as comments in the merge + request. 1. Bitbucket Server allows multiple levels of threading. GitLab -import will collapse this into one discussion and quote part of the original -comment. + import will collapse this into one discussion and quote part of the original + comment. 1. Declined pull requests have unreachable commits, which prevents the GitLab -importer from generating a proper diff. These pull requests will show up as -empty changes. + importer from generating a proper diff. These pull requests will show up as + empty changes. 1. Attachments in Markdown are currently not imported. 1. Task lists are not imported. 1. Emoji reactions are not imported diff --git a/doc/user/project/import/fogbugz.md b/doc/user/project/import/fogbugz.md index 17222c53675..13409c93929 100644 --- a/doc/user/project/import/fogbugz.md +++ b/doc/user/project/import/fogbugz.md @@ -23,6 +23,6 @@ users to GitLab users. ![Import Project](img/fogbugz_import_select_project.png) 1. Once the import has finished click the link to take you to the project -dashboard. Follow the directions to push your existing repository. + dashboard. Follow the directions to push your existing repository. ![Finished](img/fogbugz_import_finished.png) diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md index cf99dded5e2..63b90dd76fd 100644 --- a/doc/user/project/import/github.md +++ b/doc/user/project/import/github.md @@ -67,7 +67,7 @@ developer documentation. Before you begin, ensure that any GitHub users who you want to map to GitLab users have either: - A GitLab account that has logged in using the GitHub icon -\- or - + \- or - - A GitLab account with an email address that matches the [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) of the GitHub user User-matching attempts occur in that order, and if a user is not identified either way, the activity is associated with diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 6a1aadf058e..c62de41c539 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -19,7 +19,7 @@ When you create a project in GitLab, you'll have access to a large number of - [Issue Boards](issue_board.md): Organize and prioritize your workflow - [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]** - [Repositories](repository/index.md): Host your code in a fully -integrated platform + integrated platform - [Branches](repository/branches/index.md): use Git branching strategies to collaborate on code - [Protected branches](protected_branches.md): Prevent collaborators @@ -29,7 +29,7 @@ integrated platform - [Signing commits](gpg_signed_commits/index.md): use GPG to sign your commits - [Deploy tokens](deploy_tokens/index.md): Manage project-based deploy tokens that allow permanent access to the repository and Container Registry. - [Merge Requests](merge_requests/index.md): Apply your branching -strategy and get reviewed by your team + strategy and get reviewed by your team - [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html): Ask for approval before implementing a change **[STARTER]** - [Fix merge conflicts from the UI](merge_requests/resolve_conflicts.md): @@ -38,13 +38,13 @@ strategy and get reviewed by your team of the changes proposed in a merge request in a per-branch basis - [Labels](labels.md): Organize issues and merge requests by labels - [Time Tracking](../../workflow/time_tracking.md): Track estimate time -and time spent on + and time spent on the conclusion of an issue or merge request - [Milestones](milestones/index.md): Work towards a target date - [Description templates](description_templates.md): Define context-specific -templates for issue and merge request description fields for your project + templates for issue and merge request description fields for your project - [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for -common actions on issues or merge requests + common actions on issues or merge requests - [Web IDE](web_ide/index.md) **GitLab CI/CD:** @@ -68,7 +68,7 @@ common actions on issues or merge requests - [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project with a Kubernetes cluster - [GitLab Pages](pages/index.md): Build, test, and deploy your static -website with GitLab Pages + website with GitLab Pages **Other features:** @@ -76,11 +76,11 @@ website with GitLab Pages - [Snippets](../snippets.md): store, share and collaborate on code snippets. - [Cycle Analytics](cycle_analytics.md): review your development lifecycle. - [Syntax highlighting](highlighting.md): an alternative to customize -your code blocks, overriding GitLab's default choice of language. + your code blocks, overriding GitLab's default choice of language. - [Badges](badges.md): badges for the project overview. - [Releases](releases/index.md): a way to track deliverables in your project as snapshot in time of -the source, build output, and other metadata or artifacts -associated with a released version of your code. + the source, build output, and other metadata or artifacts + associated with a released version of your code. ### Project's integrations @@ -96,7 +96,7 @@ Learn how to [create a new project](../../gitlab-basics/create-project.md) in Gi You can [fork a project](../../gitlab-basics/fork-project.md) in order to: - Collaborate on code by forking a project and creating a merge request -from your fork to the upstream project + from your fork to the upstream project - Fork a sample project to work on the top of that ## Project settings diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md index f220fa8497a..4fb753d1707 100644 --- a/doc/user/project/integrations/irker.md +++ b/doc/user/project/integrations/irker.md @@ -30,12 +30,12 @@ need to follow the firsts steps of the next section. 1. Click "Irker". 1. Select the "Active" checkbox. 1. Enter the server host address where `irkerd` runs (defaults to `localhost`) -in the `Server host` field on the Web page + in the `Server host` field on the Web page 1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the -`Server port` field on the Web page. + `Server port` field on the Web page. 1. Optional: if `Default IRC URI` is set, it has to be in the format -`irc[s]://domain.name` and will be prepend to each and every channel provided -by the user which is not a full URI. + `irc[s]://domain.name` and will be prepend to each and every channel provided + by the user which is not a full URI. 1. Specify the recipients (e.g. #channel1, user1, etc.) 1. Save or optionally click "Test Settings". diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md index cec9018b67f..e2f23827360 100644 --- a/doc/user/project/integrations/project_services.md +++ b/doc/user/project/integrations/project_services.md @@ -50,6 +50,7 @@ Click on the service links to see further configuration instructions and details | [Prometheus](prometheus.md) | Monitor the performance of your deployed apps | | Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop | | [Redmine](redmine.md) | Redmine issue tracker | +| [YouTrack](youtrack.md) | YouTrack issue tracker | ## Services templates diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md index 76a2617125e..8112aa21859 100644 --- a/doc/user/project/integrations/redmine.md +++ b/doc/user/project/integrations/redmine.md @@ -1,9 +1,9 @@ # Redmine Service 1. To enable the Redmine integration in a project, navigate to the -[Integrations page](project_services.md#accessing-the-project-services), click -the **Redmine** service, and fill in the required details on the page as described -in the table below. + [Integrations page](project_services.md#accessing-the-project-services), click + the **Redmine** service, and fill in the required details on the page as described + in the table below. | Field | Description | | ----- | ----------- | diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index cbf08a4f30a..c3fc6d4b859 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -48,9 +48,9 @@ Navigate to the webhooks page by going to your project's ## Use-cases - You can set up a webhook in GitLab to send a notification to -[Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example + [Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example - You can [integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/) -every time an issue is created for a specific project or group within GitLab + every time an issue is created for a specific project or group within GitLab - You can use them to [automatically assign labels to merge requests](https://about.gitlab.com/2016/08/19/applying-gitlab-labels-automatically/). ## Webhook endpoint tips diff --git a/doc/user/project/integrations/youtrack.md b/doc/user/project/integrations/youtrack.md new file mode 100644 index 00000000000..2ab14a8db2c --- /dev/null +++ b/doc/user/project/integrations/youtrack.md @@ -0,0 +1,31 @@ +# YouTrack Service + +JetBrains YouTrack is a web-based issue tracking and project management platform. +Please refer official [documentation](https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Documentation.html) for details about YouTrack itself. + + +1. To enable the YouTrack integration in a project, navigate to the +[Integrations page](project_services.md#accessing-the-project-services), click +the **YouTrack** service, and fill in the required details on the page as described +in the table below. + + | Field | Description | + | ----- | ----------- | + | `description` | A name for the issue tracker (to differentiate between instances, for example) | + | `project_url` | The URL to the project in YouTrack which is being linked to this GitLab project | + | `issues_url` | The URL to the issue in YouTrack project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. | + + Once you have configured and enabled YouTrack you'll see the YouTrack link on the GitLab project pages that takes you to the appropriate YouTrack project. + +1. To disable the internal issue tracking system in a project, navigate to the General page, expand [Permissions](../settings/index.md#sharing-and-permissions), and slide the Issues switch invalid. + + ![Issue configuration](img/issue_configuration.png) + +## Referencing issues in YouTrack + +Issues in YouTrack can be referenced as `<PROJECT>-<ID>` where `<PROJECT>` +starts with a capital letter which is then followed by capital or lower case +letters, numbers or underscores, and `<ID>` is a number (example `Api_32-143`). + +`<PROJECT>` part is included into issue_id and links can point any YouTrack +project (`issues_url` + issue_id) diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 7962eeada5c..0bd565547c3 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -78,9 +78,9 @@ with labels, and from there organize and prioritize them with Issue Boards. For example, let's consider this simplified development workflow: 1. You have a repository hosting your app's codebase -and your team actively contributing to code + and your team actively contributing to code 1. Your **backend** team starts working a new -implementation, gathers feedback and approval, and pass it over to **frontend** + implementation, gathers feedback and approval, and pass it over to **frontend** 1. When frontend is complete, the new feature is deployed to **staging** to be tested 1. When successful, it is deployed to **production** @@ -88,7 +88,7 @@ If we have the labels "**backend**", "**frontend**", "**staging**", and "**production**", and an Issue Board with a list for each, we can: - Visualize the entire flow of implementations since the -beginning of the development lifecycle until deployed to production + beginning of the development lifecycle until deployed to production - Prioritize the issues in a list by moving them vertically - Move issues between lists to organize them according to the labels you've set - Add multiple issues to lists in the board by selecting one or more existing issues @@ -177,7 +177,7 @@ menu from where you can create another Issue Board and rename or delete the existing one. Using the search box at the top of the menu, you can filter the listed boards. -When you're revisiting an issue board in a project or group with multiple boards, +When you're revisiting an issue board in a project or group with multiple boards, GitLab will automatically load the last board you visited. NOTE: **Note:** @@ -234,7 +234,7 @@ group-level objects are available. NOTE: **Note:** Multiple group issue boards were originally introduced in [GitLab 10.0 Premium](https://about.gitlab.com/2017/09/22/gitlab-10-0-released/#group-issue-boards) and -one group issue board per group was made available in GitLab 10.6 Core. +one group issue board per group was made available in GitLab 10.6 Core. ![Group issue board](img/group_issue_board.png) diff --git a/doc/user/project/issues/automatic_issue_closing.md b/doc/user/project/issues/automatic_issue_closing.md index afb7d9ada5f..c3e06b219ff 100644 --- a/doc/user/project/issues/automatic_issue_closing.md +++ b/doc/user/project/issues/automatic_issue_closing.md @@ -1,6 +1,7 @@ # Automatic issue closing >**Notes:** +> > - This is the user docs. In order to change the default issue closing pattern, > follow the steps in the [administration docs]. > - For performance reasons, automatic issue closing is disabled for the very diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md index 2bf4fa287e9..40040e44d64 100644 --- a/doc/user/project/issues/create_new_issue.md +++ b/doc/user/project/issues/create_new_issue.md @@ -79,6 +79,6 @@ in the same URL (since a description template just populates the description fie Follow these examples to form your new issue URL with prefilled fields. - For a new issue in the GitLab Community Edition project with a pre-entered title -and a pre-entered description, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea` + and a pre-entered description, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea` - For a new issue in the GitLab Community Edition project with a pre-entered title -and a pre-entered description template, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issuable_template=Research%20proposal` + and a pre-entered description template, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issuable_template=Research%20proposal` diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md index 93306437c6c..7972c14c1c4 100644 --- a/doc/user/project/issues/due_dates.md +++ b/doc/user/project/issues/due_dates.md @@ -42,6 +42,7 @@ server's timezone. Issues with due dates can also be exported as an iCalendar feed. The URL of the feed can be added to calendar applications. The feed is accessible by clicking on the _Subscribe to calendar_ button on the following pages: + - on the **Assigned Issues** page that is linked on the right-hand side of the GitLab header - on the **Project Issues** page diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index 5a3ac9c175b..907a305fe23 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -155,7 +155,7 @@ For further details, see [Importing issues from CSV](csv_import.md) Alternatively to GitLab's built-in Issue Tracker, you can also use an [external tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine, -or Bugzilla. +YouTrack, or Bugzilla. ### Issue API diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md index e4a3ff52e07..27b9dc51760 100644 --- a/doc/user/project/issues/issues_functionalities.md +++ b/doc/user/project/issues/issues_functionalities.md @@ -99,16 +99,16 @@ Learn more in the [Issue Weight documentation](https://docs.gitlab.com/ee/workfl #### 10. Notifications - Subscribe: if you are not a participant of the discussion on that issue, but -want to receive notifications on each new input, subscribe to it. + want to receive notifications on each new input, subscribe to it. - Unsubscribe: if you are receiving notifications on that issue but no -longer want to receive them, unsubscribe from it. + longer want to receive them, unsubscribe from it. Read more in the [notifications documentation](../../../workflow/notifications.md#issue--merge-request-events). #### 11. Reference - A quick "copy to clipboard" button for that issue's reference, `foo/bar#xxx`, where `foo` is the `username` or `groupname`, `bar` -is the `project-name`, and `xxx` is the issue number. + is the `project-name`, and `xxx` is the issue number. #### 12. Title and description @@ -138,7 +138,7 @@ interpreted as spam. #### 14. Related Merge Requests - Any merge requests mentioned in that issue's description -or in the issue discussion thread. + or in the issue discussion thread. #### 15. Award emoji @@ -152,8 +152,8 @@ know you like it without spamming them. #### 16. Thread - Comments: collaborate to that issue 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). #### 17. Comment, start a discussion, or comment and close @@ -166,7 +166,7 @@ Once you write a comment, you can either: #### 18. New Merge Request - Create a new merge request (with a new source branch named after the issue) in one action. -The merge request will automatically inherit the milestone and labels of the issue. The merge -request will automatically close that issue when it is merged. + The merge request will automatically inherit the milestone and labels of the issue. The merge + request will automatically close that issue when it is merged. - Optionally, you can just create a [new branch](../repository/web_editor.md#create-a-new-branch-from-an-issue) -named after that issue. + named after that issue. diff --git a/doc/user/project/operations/error_tracking.md b/doc/user/project/operations/error_tracking.md index 90bb92d2062..41aa5b91aa7 100644 --- a/doc/user/project/operations/error_tracking.md +++ b/doc/user/project/operations/error_tracking.md @@ -21,7 +21,7 @@ GitLab provides an easy way to connect Sentry to your project: 1. Sign up to Sentry.io or [deploy your own](#deploying-sentry) Sentry instance. 1. [Find or generate](https://docs.sentry.io/api/auth/) a Sentry auth token for your Sentry project. -Make sure to give the token at least the following scopes: `event:read` and `project:read`. + Make sure to give the token at least the following scopes: `event:read` and `project:read`. 1. Navigate to your project’s **Settings > Operations** and provide the Sentry API URL and auth token. 1. Ensure that the 'Active' checkbox is set. 1. Click **Save changes** for the changes to take effect. diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md index e4ee2f7cdfa..05d5a2fd99a 100644 --- a/doc/user/project/pages/getting_started_part_four.md +++ b/doc/user/project/pages/getting_started_part_four.md @@ -376,11 +376,11 @@ manually in the past. Read through the to understand how to go even further on your scripts. - On this blog post, understand the concept of -[using GitLab CI `environments` to deploy your -web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/). + [using GitLab CI `environments` to deploy your + web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/). - On this post, learn [how to run jobs sequentially, -in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/) + in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/) - On this blog post, we go through the process of -[pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) -to deploy this website you're looking at, docs.gitlab.com. + [pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/) + to deploy this website you're looking at, docs.gitlab.com. - On this blog post, we teach you [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/). diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md index 595241b2cba..9a95fb70964 100644 --- a/doc/user/project/pages/getting_started_part_one.md +++ b/doc/user/project/pages/getting_started_part_one.md @@ -77,41 +77,41 @@ Learn more about [namespaces](../../group/index.md#namespaces). #### Project Websites - You created a project called `blog` under your username `john`, -therefore your project URL is `https://gitlab.com/john/blog/`. -Once you enable GitLab Pages for this project, and build your site, -it will be available under `https://john.gitlab.io/blog/`. + therefore your project URL is `https://gitlab.com/john/blog/`. + Once you enable GitLab Pages for this project, and build your site, + it will be available under `https://john.gitlab.io/blog/`. - You created a group for all your websites called `websites`, -and a project within this group is called `blog`. Your project -URL is `https://gitlab.com/websites/blog/`. Once you enable -GitLab Pages for this project, the site will live under -`https://websites.gitlab.io/blog/`. + and a project within this group is called `blog`. Your project + URL is `https://gitlab.com/websites/blog/`. Once you enable + GitLab Pages for this project, the site will live under + `https://websites.gitlab.io/blog/`. - You created a group for your engineering department called `engineering`, -a subgroup for all your documentation websites called `docs`, -and a project within this subgroup is called `workflows`. Your project -URL is `https://gitlab.com/engineering/docs/workflows/`. Once you enable -GitLab Pages for this project, the site will live under -`https://engineering.gitlab.io/docs/workflows`. + a subgroup for all your documentation websites called `docs`, + and a project within this subgroup is called `workflows`. Your project + URL is `https://gitlab.com/engineering/docs/workflows/`. Once you enable + GitLab Pages for this project, the site will live under + `https://engineering.gitlab.io/docs/workflows`. #### User and Group Websites - Under your username, `john`, you created a project called -`john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`. -Once you enable GitLab Pages for your project, your website -will be published under `https://john.gitlab.io`. + `john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`. + Once you enable GitLab Pages for your project, your website + will be published under `https://john.gitlab.io`. - Under your group `websites`, you created a project called -`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. -Once you enable GitLab Pages for your project, -your website will be published under `https://websites.gitlab.io`. + `websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. + Once you enable GitLab Pages for your project, + your website will be published under `https://websites.gitlab.io`. > Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8. **General example:** - On GitLab.com, a project site will always be available under -`https://namespace.gitlab.io/project-name` + `https://namespace.gitlab.io/project-name` - On GitLab.com, a user or group website will be available under -`https://namespace.gitlab.io/` + `https://namespace.gitlab.io/` - On your GitLab instance, replace `gitlab.io` above with your -Pages server domain. Ask your sysadmin for this information. + Pages server domain. Ask your sysadmin for this information. _Read on about [Projects for GitLab Pages and URL structure](getting_started_part_two.md)._ diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index b2da1c85c62..daae2f4b5a3 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -19,7 +19,7 @@ To use one or more custom domain with your Pages site, there are two things you should consider first, which we'll cover in this guide: 1. Either if you're adding a **root domain** or a **subdomain**, for which -you'll need to set up [DNS records](#dns-records) + you'll need to set up [DNS records](#dns-records) 1. Whether you want to add an [SSL/TLS certificate](#ssl-tls-certificates) or not To finish the association, you need to [add your domain to your project's Pages settings](#add-your-custom-domain-to-gitlab-pages-settings). @@ -136,13 +136,13 @@ verify your domain's ownership with a TXT record: > **Notes**: > > - **Do not** use a CNAME record if you want to point your -`domain.com` to your GitLab Pages site. Use an `A` record instead. + `domain.com` to your GitLab Pages site. Use an `A` record instead. > - **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/`. + 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 [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 @@ -278,15 +278,15 @@ These fields are found under your **Project**'s **Settings** > **Pages** > **New ### What's what? - A PEM certificate is the certificate generated by the CA, -which needs to be added to the field **Certificate (PEM)**. + which needs to be added to the field **Certificate (PEM)**. - An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority) (aka "root certificate") is -the part of the encryption keychain that identifies the CA. -Usually it's combined with the PEM certificate, but there are -some cases in which you need to add them manually. -[CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) -are one of these cases. + the part of the encryption keychain that identifies the CA. + Usually it's combined with the PEM certificate, but there are + some cases in which you need to add them manually. + [CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) + are one of these cases. - A private key is an encrypted key which validates -your PEM against your domain. + your PEM against your domain. ### Now what? @@ -295,9 +295,9 @@ of this, it's simple: - 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. + 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 >**Note:** diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md index c9081a6d72b..644a1c951d3 100644 --- a/doc/user/project/pages/getting_started_part_two.md +++ b/doc/user/project/pages/getting_started_part_two.md @@ -16,7 +16,7 @@ To get started with GitLab Pages, you need: 1. A project 1. A configuration file (`.gitlab-ci.yml`) to deploy your site 1. A specific `job` called `pages` in the configuration file -that will make GitLab aware that you are deploying a GitLab Pages website + that will make GitLab aware that you are deploying a GitLab Pages website 1. A `public` directory with the content of the website Optional Features: @@ -85,16 +85,16 @@ is useful for submitting merge requests to the upstream. ### Create a project from scratch 1. From your **Project**'s **[Dashboard](https://gitlab.com/dashboard/projects)**, -click **New project**, and name it considering the -[practical examples](getting_started_part_one.md#practical-examples). + click **New project**, and name it considering the + [practical examples](getting_started_part_one.md#practical-examples). 1. Clone it to your local computer, add your website -files to your project, add, commit and push to GitLab. + files to your project, add, commit and push to GitLab. 1. From the your **Project**'s page, click **Set up CI/CD**: ![setup GitLab CI/CD](img/setup_ci.png) 1. Choose one of the templates from the dropbox menu. -Pick up the template corresponding to the SSG you're using (or plain HTML). + Pick up the template corresponding to the SSG you're using (or plain HTML). ![gitlab-ci templates](img/choose_ci_template.png) @@ -107,20 +107,20 @@ where you'll find its default URL. > **Notes:** > > - GitLab Pages [supports any SSG](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/), but, -if you don't find yours among the templates, you'll need -to configure your own `.gitlab-ci.yml`. To do that, please -read through the article [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among -the [example projects](https://gitlab.com/pages). If you set -up a new one, please -[contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md) -to our examples. + if you don't find yours among the templates, you'll need + to configure your own `.gitlab-ci.yml`. To do that, please + read through the article [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among + the [example projects](https://gitlab.com/pages). If you set + up a new one, please + [contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md) + to our examples. > > - The second step _"Clone it to your local computer"_, can be done -differently, achieving the same results: instead of cloning the bare -repository to you local computer and moving your site files into it, -you can run `git init` in your local website directory, add the -remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`, -then add, commit, and push. + differently, achieving the same results: instead of cloning the bare + repository to you local computer and moving your site files into it, + you can run `git init` in your local website directory, add the + remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`, + then add, commit, and push. ## URLs and Baseurls diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 2de3fb7e080..e0b78753e21 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -91,13 +91,13 @@ site under the HTTPS protocol. ## Getting started -To get started with GitLab Pages, you can either [create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch), +To get started with GitLab Pages, you can either [create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch), use a [bundled template](getting_started_part_two.md#use-one-of-the-popular-pages-templates-bundled-with-gitlab), or copy any of our existing example projects: 1. Choose an [example project](https://gitlab.com/pages) to [fork](../../../gitlab-basics/fork-project.md#how-to-fork-a-project): by forking a project, you create a copy of the codebase you're forking from to start from a template instead of starting from scratch. 1. From the left sidebar, navigate to your project's **CI/CD > Pipelines** and click -**Run pipeline** so that GitLab CI/CD will build and deploy your site to the server. + **Run pipeline** so that GitLab CI/CD will 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**. @@ -164,7 +164,7 @@ with Pages, read through this series: ### GitLab Pages with SSL/TLS certificates -If you're using GitLab Pages default domain (`.gitlab.io`), your website will be +If you're using GitLab Pages default domain (`.gitlab.io`), your website will be automatically secure and available under HTTPS. If you're using your own domain, you can optionally secure it with SSL/TLS certificates. You can read the following tutorials to learn how to use these third-party certificates with GitLab Pages: diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index fa65a206273..23eb88fd305 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -151,7 +151,7 @@ Depending on how you plan to publish your website, the steps defined in the Be aware that Pages are by default branch/tag agnostic and their deployment relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the -`pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except), +`pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except-simplified), whenever a new commit is pushed to whatever branch or tag, the Pages will be overwritten. In the example below, we limit the Pages to be deployed whenever a commit is pushed only on the `master` branch: @@ -252,7 +252,7 @@ get you started. Remember that GitLab Pages are by default branch/tag agnostic and their deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit -the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except), +the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except-simplified), whenever a new commit is pushed to a branch that will be used specifically for your pages. diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md index 4ab66063dcf..bf939dbdaa3 100644 --- a/doc/user/project/pipelines/job_artifacts.md +++ b/doc/user/project/pipelines/job_artifacts.md @@ -195,5 +195,5 @@ artifacts and the job's trace. In order to retrieve a job artifact of a different project, you might need to use a private token in order to [authenticate and download](../../../api/jobs.md#get-job-artifacts) the artifacts. -[expiry date]: ../../../ci/yaml/README.md#artifacts-expire_in +[expiry date]: ../../../ci/yaml/README.md#artifactsexpire_in [ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399
\ No newline at end of file diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md index ec8b8444d99..58a0fbc97cd 100644 --- a/doc/user/project/pipelines/schedules.md +++ b/doc/user/project/pipelines/schedules.md @@ -1,4 +1,4 @@ -# Pipeline Schedules +# Pipeline schedules > **Notes**: > - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533]. diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md index cce330aecc7..4a989afad4d 100644 --- a/doc/user/project/pipelines/settings.md +++ b/doc/user/project/pipelines/settings.md @@ -23,7 +23,7 @@ in `.gitlab-ci.yml`. ## Timeout Timeout defines the maximum amount of time in minutes that a job is able run. -This is configureable under your project's **Settings > CI/CD > General pipelines settings**. +This is configurable under your project's **Settings > CI/CD > General pipelines settings**. The default value is 60 minutes. Decrease the time limit if you want to impose a hard limit on your jobs' running time or increase it otherwise. In any case, if the job surpasses the threshold, it is marked as failed. @@ -134,7 +134,7 @@ Depending on the status of your job, a badge can have the following values: You can access a pipeline status badge image using the following link: ```text -https://example.gitlab.com/<namespace>/<project>/badges/<branch>/build.svg +https://example.gitlab.com/<namespace>/<project>/badges/<branch>/pipeline.svg ``` ### Test coverage report badge diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index fac5975a0dc..22d912cd9d1 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -146,9 +146,9 @@ you are introducing those changes to your branch. Via command line, you can commit multiple times before pushing. - **Commit message:** -A commit message is important to identity what is being changed and, -more importantly, why. In GitLab, you can add keywords to the commit -message that will perform one of the actions below: + A commit message is important to identity what is being changed and, + more importantly, why. In GitLab, you can add keywords to the commit + message that will perform one of the actions below: - **Trigger a GitLab CI/CD pipeline:** If you have your project configured with [GitLab CI/CD](../../../ci/README.md), you will trigger a pipeline per push, not per commit. @@ -162,14 +162,14 @@ message that will perform one of the actions below: If you mention an issue or a merge request in a commit message, they will be shown on their respective thread. - **Cherry-pick a commit:** -In GitLab, you can -[cherry-pick a commit](../merge_requests/cherry_pick_changes.md#cherry-picking-a-commit) -right from the UI. + In GitLab, you can + [cherry-pick a commit](../merge_requests/cherry_pick_changes.md#cherry-picking-a-commit) + right from the UI. - **Revert a commit:** -Easily [revert a commit](../merge_requests/revert_changes.md#reverting-a-commit) -from the UI to a selected branch. + Easily [revert a commit](../merge_requests/revert_changes.md#reverting-a-commit) + from the UI to a selected branch. - **Sign a commit:** -Use GPG to [sign your commits](gpg_signed_commits/index.md). + Use GPG to [sign your commits](gpg_signed_commits/index.md). ## Repository size diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index 3e85e97d7a5..46a1b2bc3aa 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -24,21 +24,21 @@ file path fragments to start seeing results. ## Syntax highlighting -As expected from an IDE, syntax highlighting for many languages within +As expected from an IDE, syntax highlighting for many languages within the Web IDE will make your direct editing even easier. The Web IDE currently provides: -- Basic syntax colorization for a variety of programming, scripting and markup -languages such as XML, PHP, C#, C++, Markdown, Java, VB, Batch, Python, Ruby -and Objective-C. -- IntelliSense and validation support (displaying errors and warnings, providing -smart completions, formatting, and outlining) for some languages. For example: -TypeScript, JavaScript, CSS, LESS, SCSS, JSON and HTML. +- Basic syntax colorization for a variety of programming, scripting and markup + languages such as XML, PHP, C#, C++, Markdown, Java, VB, Batch, Python, Ruby + and Objective-C. +- IntelliSense and validation support (displaying errors and warnings, providing + smart completions, formatting, and outlining) for some languages. For example: +TypeScript, JavaScript, CSS, LESS, SCSS, JSON and HTML. -Because the Web IDE is based on the [Monaco Editor](https://microsoft.github.io/monaco-editor/), -you can find a more complete list of supported languages in the -[Monaco languages](https://github.com/Microsoft/monaco-languages) repository. +Because the Web IDE is based on the [Monaco Editor](https://microsoft.github.io/monaco-editor/), +you can find a more complete list of supported languages in the +[Monaco languages](https://github.com/Microsoft/monaco-languages) repository. NOTE: **Note:** Single file editing is based on the [Ace Editor](https://ace.c9.io). diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md index a14df6c8402..9aa81e33fc0 100644 --- a/doc/user/reserved_names.md +++ b/doc/user/reserved_names.md @@ -5,6 +5,7 @@ existing routes used by GitLab. For a list of words that are not allowed to be used as group or project names, see the [`path_regex.rb` file][reserved] under the `TOP_LEVEL_ROUTES`, `PROJECT_WILDCARD_ROUTES` and `GROUP_ROUTES` lists: + - `TOP_LEVEL_ROUTES`: are names that are reserved as usernames or top level groups - `PROJECT_WILDCARD_ROUTES`: are names that are reserved for child groups or projects. - `GROUP_ROUTES`: are names that are reserved for all groups or projects. diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 7359e1c6119..6ed6b0bda66 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -82,6 +82,8 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | <kbd>r</kbd> | Reply (quoting selected text) | | <kbd>e</kbd> | Edit issue/merge request | | <kbd>l</kbd> | Change label | +| <kbd>]</kbd> or <kbd>j</kbd> | Move to next file | +| <kbd>[</kbd> or <kbd>k</kbd> | Move to previous file | ## Wiki pages diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md index ad43189148c..e60b6819bf1 100644 --- a/doc/workflow/time_tracking.md +++ b/doc/workflow/time_tracking.md @@ -11,7 +11,7 @@ Time Tracking lets you: - Record the time spent working on an issue or a merge request. - Add an estimate of the amount of time needed to complete an issue or a merge -request. + request. You don't have to indicate an estimate to enter the time spent, and vice versa. diff --git a/jest.config.js b/jest.config.js index 4dab7c2891a..225f01de303 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,11 +13,15 @@ if (process.env.CI) { // eslint-disable-next-line import/no-commonjs module.exports = { - testMatch: ['<rootDir>/spec/frontend/**/*_spec.js'], + testMatch: ['<rootDir>/spec/frontend/**/*_spec.js', '<rootDir>/ee/spec/frontend/**/*_spec.js'], moduleFileExtensions: ['js', 'json', 'vue'], moduleNameMapper: { '^~(.*)$': '<rootDir>/app/assets/javascripts$1', + '^ee(.*)$': '<rootDir>/ee/app/assets/javascripts$1', + '^fixtures(.*)$': '<rootDir>/spec/javascripts/fixtures$1', '^helpers(.*)$': '<rootDir>/spec/frontend/helpers$1', + '^vendor(.*)$': '<rootDir>/vendor/assets/javascripts$1', + '\\.(jpg|jpeg|png|svg)$': '<rootDir>/spec/frontend/__mocks__/file_mock.js', }, collectCoverageFrom: ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'], coverageDirectory: '<rootDir>/coverage-frontend/', @@ -28,7 +32,9 @@ module.exports = { setupTestFrameworkScriptFile: '<rootDir>/spec/frontend/test_setup.js', restoreMocks: true, transform: { + '^.+\\.(gql|graphql)$': 'jest-transform-graphql', '^.+\\.js$': 'babel-jest', '^.+\\.vue$': 'vue-jest', }, + transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui)/)'], }; diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9199f898ea0..26ca90c02cb 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -473,6 +473,12 @@ module API expose(:project_id) { |entity| entity&.project.try(:id) } expose :title, :description expose :state, :created_at, :updated_at + + # Avoids an N+1 query when metadata is included + def issuable_metadata(subject, options, method) + cached_subject = options.dig(:issuable_metadata, subject.id) + (cached_subject || subject).public_send(method) # rubocop: disable GitlabSecurity/PublicSend + end end class Diff < Grape::Entity @@ -518,54 +524,32 @@ module API class IssueBasic < ProjectEntity expose :closed_at expose :closed_by, using: Entities::UserBasic - expose :labels do |issue, options| + expose :labels do |issue| # Avoids an N+1 query since labels are preloaded issue.labels.map(&:title).sort end expose :milestone, using: Entities::Milestone expose :assignees, :author, using: Entities::UserBasic - expose :assignee, using: ::API::Entities::UserBasic do |issue, options| + expose :assignee, using: ::API::Entities::UserBasic do |issue| issue.assignees.first end - expose :user_notes_count - expose :upvotes do |issue, options| - if options[:issuable_metadata] - # Avoids an N+1 query when metadata is included - options[:issuable_metadata][issue.id].upvotes - else - issue.upvotes - end - end - expose :downvotes do |issue, options| - if options[:issuable_metadata] - # Avoids an N+1 query when metadata is included - options[:issuable_metadata][issue.id].downvotes - else - issue.downvotes - end - end + expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) } + expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count) } + expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) } + expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) } expose :due_date expose :confidential expose :discussion_locked - expose :web_url do |issue, options| + expose :web_url do |issue| Gitlab::UrlBuilder.build(issue) end expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue| issue end - - expose :merge_requests_count do |issue, options| - if options[:issuable_metadata] - # Avoids an N+1 query when metadata is included - options[:issuable_metadata][issue.id].merge_requests_count - else - issue.merge_requests_closing_issues.count - end - end end class Issue < IssueBasic @@ -659,23 +643,12 @@ module API MarkupHelper.markdown_field(entity, :description) end expose :target_branch, :source_branch - expose :upvotes do |merge_request, options| - if options[:issuable_metadata] - options[:issuable_metadata][merge_request.id].upvotes - else - merge_request.upvotes - end - end - expose :downvotes do |merge_request, options| - if options[:issuable_metadata] - options[:issuable_metadata][merge_request.id].downvotes - else - merge_request.downvotes - end - end + expose(:user_notes_count) { |merge_request, options| issuable_metadata(merge_request, options, :user_notes_count) } + expose(:upvotes) { |merge_request, options| issuable_metadata(merge_request, options, :upvotes) } + expose(:downvotes) { |merge_request, options| issuable_metadata(merge_request, options, :downvotes) } expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id - expose :labels do |merge_request, options| + expose :labels do |merge_request| # Avoids an N+1 query since labels are preloaded merge_request.labels.map(&:title).sort end @@ -693,7 +666,6 @@ module API end expose :diff_head_sha, as: :sha expose :merge_commit_sha - expose :user_notes_count expose :discussion_locked expose :should_remove_source_branch?, as: :should_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch @@ -701,7 +673,7 @@ module API # Deprecated expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? } - expose :web_url do |merge_request, options| + expose :web_url do |merge_request| Gitlab::UrlBuilder.build(merge_request) end @@ -1385,13 +1357,9 @@ module API class GitInfo < Grape::Entity expose :repo_url, :ref, :sha, :before_sha - expose :ref_type do |model| - if model.tag - 'tag' - else - 'branch' - end - end + expose :ref_type + expose :refspecs + expose :git_depth, as: :depth end class RunnerInfo < Grape::Entity diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb index de59c915d66..d00e61678b5 100644 --- a/lib/api/helpers/pagination.rb +++ b/lib/api/helpers/pagination.rb @@ -13,6 +13,33 @@ module API strategy.new(self).paginate(relation) end + class Base + private + + def per_page + @per_page ||= params[:per_page] + end + + def base_request_uri + @base_request_uri ||= URI.parse(request.url).tap do |uri| + uri.host = Gitlab.config.gitlab.host + uri.port = nil + end + end + + def build_page_url(query_params:) + base_request_uri.tap do |uri| + uri.query = query_params + end.to_s + end + + def page_href(next_page_params = {}) + query_params = params.merge(**next_page_params, per_page: per_page).to_query + + build_page_url(query_params: query_params) + end + end + class KeysetPaginationInfo attr_reader :relation, :request_context @@ -85,7 +112,7 @@ module API end end - class KeysetPaginationStrategy + class KeysetPaginationStrategy < Base attr_reader :request_context delegate :params, :header, :request, to: :request_context @@ -141,10 +168,6 @@ module API ] end - def per_page - params[:per_page] - end - def add_default_pagination_headers header 'X-Per-Page', per_page.to_s end @@ -154,22 +177,12 @@ module API header 'Link', link_for('next', next_page_params) end - def page_href(next_page_params) - request_url = request.url.split('?').first - request_params = params.dup - request_params[:per_page] = per_page - - request_params.merge!(next_page_params) if next_page_params - - "#{request_url}?#{request_params.to_query}" - end - def link_for(rel, next_page_params) %(<#{page_href(next_page_params)}>; rel="#{rel}") end end - class DefaultPaginationStrategy + class DefaultPaginationStrategy < Base attr_reader :request_context delegate :params, :header, :request, to: :request_context @@ -198,15 +211,13 @@ module API end end - # rubocop: disable CodeReuse/ActiveRecord def add_default_order(relation) if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty? - relation = relation.order(:id) + relation = relation.order(:id) # rubocop: disable CodeReuse/ActiveRecord end relation end - # rubocop: enable CodeReuse/ActiveRecord def add_pagination_headers(paginated_data) header 'X-Per-Page', paginated_data.limit_value.to_s @@ -222,27 +233,13 @@ module API end def pagination_links(paginated_data) - request_url = request.url.split('?').first - request_params = params.clone - request_params[:per_page] = paginated_data.limit_value - - links = [] - - request_params[:page] = paginated_data.prev_page - links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") if request_params[:page] - - request_params[:page] = paginated_data.next_page - links << %(<#{request_url}?#{request_params.to_query}>; rel="next") if request_params[:page] - - request_params[:page] = 1 - links << %(<#{request_url}?#{request_params.to_query}>; rel="first") - - unless data_without_counts?(paginated_data) - request_params[:page] = total_pages(paginated_data) - links << %(<#{request_url}?#{request_params.to_query}>; rel="last") - end + [].tap do |links| + links << %(<#{page_href(page: paginated_data.prev_page)}>; rel="prev") if paginated_data.prev_page + links << %(<#{page_href(page: paginated_data.next_page)}>; rel="next") if paginated_data.next_page + links << %(<#{page_href(page: 1)}>; rel="first") - links.join(', ') + links << %(<#{page_href(page: total_pages(paginated_data))}>; rel="last") unless data_without_counts?(paginated_data) + end.join(', ') end def total_pages(paginated_data) diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 16df8e830e1..ff73a49d5e8 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -26,7 +26,7 @@ module API end def get_runner_ip - { ip_address: request.env["HTTP_X_FORWARDED_FOR"] || request.ip } + { ip_address: env["action_dispatch.remote_ip"].to_s || request.ip } end def current_runner diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 94ed9ac6fb1..f43f4d961d6 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -54,6 +54,7 @@ module API optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' + optional :confidential, type: Boolean, desc: 'Filter confidential or public issues' use :pagination use :issues_params_ee diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index f8d2ba49d2f..03f6684226f 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -369,11 +369,11 @@ module API merge_request.update(squash: params[:squash]) if params[:squash] - merge_params = { + merge_params = HashWithIndifferentAccess.new( commit_message: params[:merge_commit_message], squash_commit_message: params[:squash_commit_message], should_remove_source_branch: params[:should_remove_source_branch] - } + ) if merge_when_pipeline_succeeds && merge_request.head_pipeline && merge_request.head_pipeline.active? ::MergeRequests::MergeWhenPipelineSucceedsService diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb index da31bcb8dac..ca24742b7a3 100644 --- a/lib/api/project_milestones.rb +++ b/lib/api/project_milestones.rb @@ -98,6 +98,23 @@ module API milestone_issuables_for(user_project, :merge_request) end + + desc 'Promote a milestone to group milestone' do + detail 'This feature was introduced in GitLab 11.9' + end + post ':id/milestones/:milestone_id/promote' do + begin + authorize! :admin_milestone, user_project + authorize! :admin_milestone, user_project.group + + milestone = user_project.milestones.find(params[:milestone_id]) + Milestones::PromoteService.new(user_project, current_user).execute(milestone) + + status(200) + rescue Milestones::PromoteService::PromoteMilestoneError => error + render_api_error!(error.message, 400) + end + end end end end diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb index d05ddad7466..119902a189c 100644 --- a/lib/api/project_templates.rb +++ b/lib/api/project_templates.rb @@ -36,7 +36,10 @@ module API optional :project, type: String, desc: 'The project name to use when expanding placeholders in the template. Only affects licenses' optional :fullname, type: String, desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses' end - get ':id/templates/:type/:name', requirements: { name: /[\w\.-]+/ } do + # The regex is needed to ensure a period (e.g. agpl-3.0) + # isn't confused with a format type. We also need to allow encoded + # values (e.g. C%2B%2B for C++), so allow % and + as well. + get ':id/templates/:type/:name', requirements: { name: /[\w%.+-]+/ } do template = TemplateFinder .build(params[:type], user_project, name: params[:name]) .execute diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 6a93ef9f3ad..b23fe6cd4e7 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -258,6 +258,8 @@ module API end params do optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' + optional :path, type: String, desc: 'The path that will be assigned to the fork' + optional :name, type: String, desc: 'The name that will be assigned to the fork' end post ':id/fork' do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42284') @@ -386,7 +388,11 @@ module API desc 'Get languages in project repository' get ':id/languages' do - user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h + if user_project.repository_languages.present? + user_project.repository_languages.map { |l| [l.name, l.share] }.to_h + else + user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h + end end desc 'Remove a project' diff --git a/lib/api/services.rb b/lib/api/services.rb index 145897516a0..bda6be51553 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -592,6 +592,26 @@ module API desc: 'The description of the tracker' } ], + 'youtrack' => [ + { + required: true, + name: :project_url, + type: String, + desc: 'The project URL' + }, + { + required: true, + name: :issues_url, + type: String, + desc: 'The issues URL' + }, + { + required: false, + name: :description, + type: String, + desc: 'The description of the tracker' + } + ], 'slack' => [ CHAT_NOTIFICATION_SETTINGS, CHAT_NOTIFICATION_FLAGS, @@ -665,6 +685,7 @@ module API PrometheusService, PushoverService, RedmineService, + YoutrackService, SlackService, MattermostService, MicrosoftTeamsService, diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 93e6d6470f1..2745905c5ff 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -150,7 +150,10 @@ module Banzai end def uri_type(path) - @uri_types[path] ||= current_commit.uri_type(path) + # https://gitlab.com/gitlab-org/gitlab-ce/issues/58011 + Gitlab::GitalyClient.allow_n_plus_1_calls do + @uri_types[path] ||= current_commit.uri_type(path) + end end def current_commit diff --git a/lib/bitbucket_server/connection.rb b/lib/bitbucket_server/connection.rb index 9c14b26c65a..fbd451efb23 100644 --- a/lib/bitbucket_server/connection.rb +++ b/lib/bitbucket_server/connection.rb @@ -77,6 +77,7 @@ module BitbucketServer private def check_errors!(response) + return if ActionDispatch::Response::NO_CONTENT_CODES.include?(response.code) raise ConnectionError, "Response is not valid JSON" unless response.parsed_response.is_a?(Hash) return if response.code >= 200 && response.code < 300 diff --git a/lib/gitlab/chat.rb b/lib/gitlab/chat.rb new file mode 100644 index 00000000000..23d4fb36b66 --- /dev/null +++ b/lib/gitlab/chat.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Gitlab + module Chat + # Returns `true` if Chatops is available for the current instance. + def self.available? + ::Feature.enabled?(:chatops, default_enabled: true) + end + end +end diff --git a/lib/gitlab/chat/command.rb b/lib/gitlab/chat/command.rb new file mode 100644 index 00000000000..49b7dcf4bbe --- /dev/null +++ b/lib/gitlab/chat/command.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +module Gitlab + module Chat + # Class for scheduling chat pipelines. + # + # A Command takes care of creating a `Ci::Pipeline` with all the data + # necessary to execute a chat command. This includes data such as the chat + # data (e.g. the response URL) and any environment variables that should be + # exposed to the chat command. + class Command + include Utils::StrongMemoize + + attr_reader :project, :chat_name, :name, :arguments, :response_url, + :channel + + # project - The Project to schedule the command for. + # chat_name - The ChatName belonging to the user that scheduled the + # command. + # name - The name of the chat command to run. + # arguments - The arguments (as a String) to pass to the command. + # channel - The channel the message was sent from. + # response_url - The URL to send the response back to. + def initialize(project:, chat_name:, name:, arguments:, channel:, response_url:) + @project = project + @chat_name = chat_name + @name = name + @arguments = arguments + @channel = channel + @response_url = response_url + end + + # Tries to create a new pipeline. + # + # This method will return a pipeline that _may_ be persisted, or `nil` if + # the pipeline could not be created. + def try_create_pipeline + return unless valid? + + create_pipeline + end + + def create_pipeline + service = ::Ci::CreatePipelineService.new( + project, + chat_name.user, + ref: branch, + sha: commit, + chat_data: { + chat_name_id: chat_name.id, + command: name, + arguments: arguments, + response_url: response_url + } + ) + + service.execute(:chat) do |pipeline| + build_environment_variables(pipeline) + build_chat_data(pipeline) + end + end + + # pipeline - The `Ci::Pipeline` to create the environment variables for. + def build_environment_variables(pipeline) + pipeline.variables.build( + [{ key: 'CHAT_INPUT', value: arguments }, + { key: 'CHAT_CHANNEL', value: channel }] + ) + end + + # pipeline - The `Ci::Pipeline` to create the chat data for. + def build_chat_data(pipeline) + pipeline.build_chat_data( + chat_name_id: chat_name.id, + response_url: response_url + ) + end + + def valid? + branch && commit + end + + def branch + strong_memoize(:branch) { project.default_branch } + end + + def commit + strong_memoize(:commit) do + project.commit(branch)&.id if branch + end + end + end + end +end diff --git a/lib/gitlab/chat/output.rb b/lib/gitlab/chat/output.rb new file mode 100644 index 00000000000..411b1555a7d --- /dev/null +++ b/lib/gitlab/chat/output.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Gitlab + module Chat + # Class for gathering and formatting the output of a `Ci::Build`. + class Output + attr_reader :build + + MissingBuildSectionError = Class.new(StandardError) + + # The primary trace section to look for. + PRIMARY_SECTION = 'chat_reply' + + # The backup trace section in case the primary one could not be found. + FALLBACK_SECTION = 'build_script' + + # build - The `Ci::Build` to obtain the output from. + def initialize(build) + @build = build + end + + # Returns a `String` containing the output of the build. + # + # The output _does not_ include the command that was executed. + def to_s + offset, length = read_offset_and_length + + trace.read do |stream| + stream.seek(offset) + + output = stream + .stream + .read(length) + .force_encoding(Encoding.default_external) + + without_executed_command_line(output) + end + end + + # Returns the offset to seek to and the number of bytes to read relative + # to the offset. + def read_offset_and_length + section = find_build_trace_section(PRIMARY_SECTION) || + find_build_trace_section(FALLBACK_SECTION) + + unless section + raise( + MissingBuildSectionError, + "The build_script trace section could not be found for build #{build.id}" + ) + end + + length = section[:byte_end] - section[:byte_start] + + [section[:byte_start], length] + end + + # Removes the line containing the executed command from the build output. + # + # output - A `String` containing the output of a trace section. + def without_executed_command_line(output) + # If `output.split("\n")` produces an empty Array then the slicing that + # follows it will produce a nil. For example: + # + # "\n".split("\n") # => [] + # "\n".split("\n")[1..-1] # => nil + # + # To work around this we only "join" if we're given an Array. + if (converted = output.split("\n")[1..-1]) + converted.join("\n") + else + '' + end + end + + # Returns the trace section for the given name, or `nil` if the section + # could not be found. + # + # name - The name of the trace section to find. + def find_build_trace_section(name) + trace_sections.find { |s| s[:name] == name } + end + + def trace_sections + @trace_sections ||= trace.extract_sections + end + + def trace + @trace ||= build.trace + end + end + end +end diff --git a/lib/gitlab/chat/responder.rb b/lib/gitlab/chat/responder.rb new file mode 100644 index 00000000000..6267fbc20e2 --- /dev/null +++ b/lib/gitlab/chat/responder.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Gitlab + module Chat + module Responder + # Returns an instance of the responder to use for generating chat + # responses. + # + # This method will return `nil` if no formatter is available for the given + # build. + # + # build - A `Ci::Build` that executed a chat command. + def self.responder_for(build) + service = build.pipeline.chat_data&.chat_name&.service + + if (responder = service.try(:chat_responder)) + responder.new(build) + end + end + end + end +end diff --git a/lib/gitlab/chat/responder/base.rb b/lib/gitlab/chat/responder/base.rb new file mode 100644 index 00000000000..f1ad0e36793 --- /dev/null +++ b/lib/gitlab/chat/responder/base.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Gitlab + module Chat + module Responder + class Base + attr_reader :build + + # build - The `Ci::Build` that was executed. + def initialize(build) + @build = build + end + + def pipeline + build.pipeline + end + + def project + pipeline.project + end + + def success(*) + raise NotImplementedError, 'You must implement #success(output)' + end + + def failure + raise NotImplementedError, 'You must implement #failure' + end + + def send_response(output) + raise NotImplementedError, 'You must implement #send_response(output)' + end + + def scheduled_output + raise NotImplementedError, 'You must implement #scheduled_output' + end + end + end + end +end diff --git a/lib/gitlab/chat/responder/slack.rb b/lib/gitlab/chat/responder/slack.rb new file mode 100644 index 00000000000..0cf02c92a67 --- /dev/null +++ b/lib/gitlab/chat/responder/slack.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module Gitlab + module Chat + module Responder + class Slack < Responder::Base + SUCCESS_COLOR = '#B3ED8E' + FAILURE_COLOR = '#FF5640' + RESPONSE_TYPE = :in_channel + + # Slack breaks messages apart if they're around 4 KB in size. We use a + # slightly smaller limit here to account for user mentions. + MESSAGE_SIZE_LIMIT = 3.5.kilobytes + + # Sends a response back to Slack + # + # output - The output to send back to Slack, as a Hash. + def send_response(output) + Gitlab::HTTP.post( + pipeline.chat_data.response_url, + { + headers: { Accept: 'application/json' }, + body: output.to_json + } + ) + end + + # Sends the output for a build that completed successfully. + # + # output - The output produced by the chat command. + def success(output) + return if output.empty? + + send_response( + text: message_text(limit_output(output)), + response_type: RESPONSE_TYPE + ) + end + + # Sends the output for a build that failed. + def failure + send_response( + text: message_text("<#{build_url}|Sorry, the build failed!>"), + response_type: RESPONSE_TYPE + ) + end + + # Returns the output to send back after a command has been scheduled. + def scheduled_output + # We return an empty message so that Slack still shows the input + # command, without polluting the channel with standard "The job has + # been scheduled" (or similar) responses. + { text: '' } + end + + private + + def limit_output(output) + if output.bytesize <= MESSAGE_SIZE_LIMIT + output + else + "<#{build_url}|The output is too large to be sent back directly!>" + end + end + + def mention_user + "<@#{pipeline.chat_data.chat_name.chat_id}>" + end + + def message_text(output) + "#{mention_user}: #{output}" + end + + def build_url + ::Gitlab::Routing.url_helpers.project_build_url(project, build) + end + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb index 41632211374..164a4634d84 100644 --- a/lib/gitlab/ci/pipeline/chain/build.rb +++ b/lib/gitlab/ci/pipeline/chain/build.rb @@ -12,6 +12,8 @@ module Gitlab ref: @command.ref, sha: @command.sha, before_sha: @command.before_sha, + source_sha: @command.source_sha, + target_sha: @command.target_sha, tag: @command.tag_exists?, trigger_requests: Array(@command.trigger_request), user: @command.current_user, diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index e62d547d862..7b77e86feae 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -7,10 +7,11 @@ module Gitlab module Chain Command = Struct.new( :source, :project, :current_user, - :origin_ref, :checkout_sha, :after_sha, :before_sha, + :origin_ref, :checkout_sha, :after_sha, :before_sha, :source_sha, :target_sha, :trigger_request, :schedule, :merge_request, :ignore_skip_ci, :save_incompleted, - :seeds_block, :variables_attributes, :push_options + :seeds_block, :variables_attributes, :push_options, + :chat_data ) do include Gitlab::Utils::StrongMemoize diff --git a/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb b/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb index 0f687a4ce9b..1e09b417311 100644 --- a/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb +++ b/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs.rb @@ -6,7 +6,13 @@ module Gitlab module Chain class RemoveUnwantedChatJobs < Chain::Base def perform! - # to be overriden in EE + return unless pipeline.config_processor && pipeline.chat? + + # When scheduling a chat pipeline we only want to run the build + # that matches the chat command. + pipeline.config_processor.jobs.select! do |name, _| + name.to_s == command.chat_data[:command].to_s + end end def break? diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index 527a432d72d..c0d4d4400b3 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -48,9 +48,10 @@ variables: POSTGRES_PASSWORD: testing-password POSTGRES_ENABLED: "true" POSTGRES_DB: $CI_ENVIRONMENT_SLUG + POSTGRES_VERSION: 9.6.2 - KUBERNETES_VERSION: 1.11.6 - HELM_VERSION: 2.12.2 + KUBERNETES_VERSION: 1.11.7 + HELM_VERSION: 2.12.3 DOCKER_DRIVER: overlay2 @@ -73,12 +74,11 @@ stages: build: stage: build - image: docker:stable-git + image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image/master:stable" services: - - docker:stable-dind + - docker:stable-dind script: - - setup_docker - - build + - /build/build.sh only: - branches @@ -490,7 +490,6 @@ rollout 100%: export DATABASE_URL=${DATABASE_URL-$auto_database_url} export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG export CI_APPLICATION_TAG=$CI_COMMIT_SHA - export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} export TILLER_NAMESPACE=$KUBE_NAMESPACE # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') @@ -700,6 +699,7 @@ rollout 100%: --set postgresql.postgresUser="$POSTGRES_USER" \ --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \ --set postgresql.postgresDatabase="$POSTGRES_DB" \ + --set postgresql.imageTag="$POSTGRES_VERSION" \ --set application.initializeCommand="$DB_INITIALIZE" \ --namespace="$KUBE_NAMESPACE" \ "$name" \ @@ -828,7 +828,7 @@ rollout 100%: # Function to ensure backwards compatibility with AUTO_DEVOPS_DOMAIN function ensure_kube_ingress_base_domain() { - if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then + if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ] && [ -n "$AUTO_DEVOPS_DOMAIN" ] ; then export KUBE_INGRESS_BASE_DOMAIN=$AUTO_DEVOPS_DOMAIN fi } @@ -849,50 +849,6 @@ rollout 100%: fi } - function build() { - registry_login - - if [[ -f Dockerfile ]]; then - echo "Building Dockerfile-based application..." - docker build \ - --build-arg HTTP_PROXY="$HTTP_PROXY" \ - --build-arg http_proxy="$http_proxy" \ - --build-arg HTTPS_PROXY="$HTTPS_PROXY" \ - --build-arg https_proxy="$https_proxy" \ - --build-arg FTP_PROXY="$FTP_PROXY" \ - --build-arg ftp_proxy="$ftp_proxy" \ - --build-arg NO_PROXY="$NO_PROXY" \ - --build-arg no_proxy="$no_proxy" \ - -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" . - else - echo "Building Heroku-based application using gliderlabs/herokuish docker image..." - docker run -i \ - -e BUILDPACK_URL \ - -e HTTP_PROXY \ - -e http_proxy \ - -e HTTPS_PROXY \ - -e https_proxy \ - -e FTP_PROXY \ - -e ftp_proxy \ - -e NO_PROXY \ - -e no_proxy \ - --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build - docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" - docker rm "$CI_CONTAINER_NAME" >/dev/null - echo "" - - echo "Configuring $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG docker image..." - docker create --expose 5000 --env PORT=5000 --name="$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" /bin/herokuish procfile start web - docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" - docker rm "$CI_CONTAINER_NAME" >/dev/null - echo "" - fi - - echo "Pushing to GitLab Container Registry..." - docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" - echo "" - } - function initialize_tiller() { echo "Checking Tiller..." diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb index 0f3f5cb3c08..d3c86fdb629 100644 --- a/lib/gitlab/danger/helper.rb +++ b/lib/gitlab/danger/helper.rb @@ -106,13 +106,13 @@ module Gitlab %r{\A(ee/)?app/(assets|views)/} => :frontend, %r{\A(ee/)?public/} => :frontend, - %r{\A(ee/)?spec/javascripts/} => :frontend, + %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend, %r{\A(ee/)?vendor/assets/} => :frontend, %r{\A(jest\.config\.js|package\.json|yarn\.lock)\z} => :frontend, %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend, %r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend, - %r{\A(ee/)?spec/(?!javascripts)[^/]+} => :backend, + %r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend, %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend, %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend, %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile|\.gitlab-ci\.yml)\z} => :backend, diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 593a3676519..aea132a3dd9 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -556,6 +556,12 @@ module Gitlab tags.find { |tag| tag.name == name } end + def merge_to_ref(user, source_sha, branch, target_ref, message) + wrapped_gitaly_errors do + gitaly_operation_client.user_merge_to_ref(user, source_sha, branch, target_ref, message) + end + end + def merge(user, source_sha, target_branch, message, &block) wrapped_gitaly_errors do gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block) diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 0ab53f8f706..5aeedb0f50d 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -28,7 +28,7 @@ module Gitlab PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION' - MAXIMUM_GITALY_CALLS = 35 + MAXIMUM_GITALY_CALLS = 30 CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze MUTEX = Mutex.new diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 22d2d149e65..d172c798da2 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -100,6 +100,25 @@ module Gitlab end end + def user_merge_to_ref(user, source_sha, branch, target_ref, message) + request = Gitaly::UserMergeToRefRequest.new( + repository: @gitaly_repo, + source_sha: source_sha, + branch: encode_binary(branch), + target_ref: encode_binary(target_ref), + user: Gitlab::Git::User.from_gitlab(user).to_gitaly, + message: message + ) + + response = GitalyClient.call(@repository.storage, :operation_service, :user_merge_to_ref, request) + + if pre_receive_error = response.pre_receive_error.presence + raise Gitlab::Git::PreReceiveError, pre_receive_error + end + + response.commit_id + end + def user_merge_branch(user, source_sha, target_branch, message) request_enum = QueueEnumerator.new response_enum = GitalyClient.call( diff --git a/lib/gitlab/graphql/authorize.rb b/lib/gitlab/graphql/authorize.rb index 5e48bf9043d..f62813db82c 100644 --- a/lib/gitlab/graphql/authorize.rb +++ b/lib/gitlab/graphql/authorize.rb @@ -10,21 +10,6 @@ module Gitlab def self.use(schema_definition) schema_definition.instrument(:field, Instrumentation.new) end - - def required_permissions - # If the `#authorize` call is used on multiple classes, we add the - # permissions specified on a subclass, to the ones that were specified - # on it's superclass. - @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions) - superclass.required_permissions.dup - else - [] - end - end - - def authorize(*permissions) - required_permissions.concat(permissions) - end end end end diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb index a56c4f6368d..b367a97105c 100644 --- a/lib/gitlab/graphql/authorize/authorize_resource.rb +++ b/lib/gitlab/graphql/authorize/authorize_resource.rb @@ -6,8 +6,21 @@ module Gitlab module AuthorizeResource extend ActiveSupport::Concern - included do - extend Gitlab::Graphql::Authorize + class_methods do + def required_permissions + # If the `#authorize` call is used on multiple classes, we add the + # permissions specified on a subclass, to the ones that were specified + # on it's superclass. + @required_permissions ||= if self.respond_to?(:superclass) && superclass.respond_to?(:required_permissions) + superclass.required_permissions.dup + else + [] + end + end + + def authorize(*permissions) + required_permissions.concat(permissions) + end end def find_object(*args) diff --git a/lib/gitlab/graphql/authorize/instrumentation.rb b/lib/gitlab/graphql/authorize/instrumentation.rb index 2a3d790d67b..593da8471dd 100644 --- a/lib/gitlab/graphql/authorize/instrumentation.rb +++ b/lib/gitlab/graphql/authorize/instrumentation.rb @@ -6,19 +6,15 @@ module Gitlab class Instrumentation # Replace the resolver for the field with one that will only return the # resolved object if the permissions check is successful. - # - # Collections are not supported. Apply permissions checks for those at the - # database level instead, to avoid loading superfluous data from the DB def instrument(_type, field) - field_definition = field.metadata[:type_class] - return field unless field_definition.respond_to?(:required_permissions) - return field if field_definition.required_permissions.empty? + required_permissions = Array.wrap(field.metadata[:authorize]) + return field if required_permissions.empty? old_resolver = field.resolve_proc new_resolver = -> (obj, args, ctx) do resolved_obj = old_resolver.call(obj, args, ctx) - checker = build_checker(ctx[:current_user], field_definition.required_permissions) + checker = build_checker(ctx[:current_user], required_permissions) if resolved_obj.respond_to?(:then) resolved_obj.then(&checker) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 7f8c6d56627..fa54fc17d95 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -28,7 +28,6 @@ project_tree: - notes: :author - releases: - - :author - :links - project_members: - :user diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb index bbac15c7710..42c4745ff98 100644 --- a/lib/gitlab/kubernetes/helm.rb +++ b/lib/gitlab/kubernetes/helm.rb @@ -3,8 +3,8 @@ module Gitlab module Kubernetes module Helm - HELM_VERSION = '2.12.2'.freeze - KUBECTL_VERSION = '1.11.0'.freeze + HELM_VERSION = '2.12.3'.freeze + KUBECTL_VERSION = '1.11.7'.freeze NAMESPACE = 'gitlab-managed-apps'.freeze SERVICE_ACCOUNT = 'tiller'.freeze CLUSTER_ROLE_BINDING = 'tiller-admin'.freeze diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb index ef656e5b2ce..29c511524a2 100644 --- a/lib/gitlab/project_template.rb +++ b/lib/gitlab/project_template.rb @@ -28,11 +28,17 @@ module Gitlab ProjectTemplate.new('rails', 'Ruby on Rails', _('Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/rails', 'illustrations/logos/rails.svg'), ProjectTemplate.new('spring', 'Spring', _('Includes an MVC structure, mvnw and pom.xml to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/spring', 'illustrations/logos/spring.svg'), ProjectTemplate.new('express', 'NodeJS Express', _('Includes an MVC structure to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/express', 'illustrations/logos/express.svg'), + ProjectTemplate.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore', 'illustrations/logos/dotnet.svg'), ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo.'), 'https://gitlab.com/pages/hugo'), ProjectTemplate.new('jekyll', 'Pages/Jekyll', _('Everything you need to create a GitLab Pages site using Jekyll.'), 'https://gitlab.com/pages/jekyll'), ProjectTemplate.new('plainhtml', 'Pages/Plain HTML', _('Everything you need to create a GitLab Pages site using plain HTML.'), 'https://gitlab.com/pages/plain-html'), ProjectTemplate.new('gitbook', 'Pages/GitBook', _('Everything you need to create a GitLab Pages site using GitBook.'), 'https://gitlab.com/pages/gitbook'), - ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo') + ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo'), + ProjectTemplate.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo', 'illustrations/logos/netlify.svg'), + ProjectTemplate.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll', 'illustrations/logos/netlify.svg'), + ProjectTemplate.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html', 'illustrations/logos/netlify.svg'), + ProjectTemplate.new('nfgitbook', 'Netlify/GitBook', _('A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfgitbook', 'illustrations/logos/netlify.svg'), + ProjectTemplate.new('nfhexo', 'Netlify/Hexo', _('A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhexo', 'illustrations/logos/netlify.svg') ].freeze class << self diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 1153e69d3de..c7d8dfcd495 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -280,7 +280,10 @@ module Gitlab # add_namespace("default", "gitlab") # def add_namespace(storage, name) - Gitlab::GitalyClient::NamespaceService.new(storage).add(name) + # https://gitlab.com/gitlab-org/gitlab-ce/issues/58012 + Gitlab::GitalyClient.allow_n_plus_1_calls do + Gitlab::GitalyClient::NamespaceService.new(storage).add(name) + end rescue GRPC::InvalidArgument => e raise ArgumentError, e.message end diff --git a/lib/gitlab/slash_commands/application_help.rb b/lib/gitlab/slash_commands/application_help.rb new file mode 100644 index 00000000000..0ea7554ba64 --- /dev/null +++ b/lib/gitlab/slash_commands/application_help.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Gitlab + module SlashCommands + class ApplicationHelp < BaseCommand + def initialize(params) + @params = params + end + + def execute + Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, params[:text]) + end + + private + + def trigger + "#{params[:command]} [project name or alias]" + end + + def commands + Gitlab::SlashCommands::Command.commands + end + end + end +end diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb index 474c09b9c4d..7c963fcf38a 100644 --- a/lib/gitlab/slash_commands/command.rb +++ b/lib/gitlab/slash_commands/command.rb @@ -9,7 +9,8 @@ module Gitlab Gitlab::SlashCommands::IssueNew, Gitlab::SlashCommands::IssueSearch, Gitlab::SlashCommands::IssueMove, - Gitlab::SlashCommands::Deploy + Gitlab::SlashCommands::Deploy, + Gitlab::SlashCommands::Run ] end diff --git a/lib/gitlab/slash_commands/presenters/error.rb b/lib/gitlab/slash_commands/presenters/error.rb new file mode 100644 index 00000000000..442f8796338 --- /dev/null +++ b/lib/gitlab/slash_commands/presenters/error.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Gitlab + module SlashCommands + module Presenters + class Error < Presenters::Base + def initialize(message) + @message = message + end + + def message + ephemeral_response(text: @message) + end + end + end + end +end diff --git a/lib/gitlab/slash_commands/presenters/run.rb b/lib/gitlab/slash_commands/presenters/run.rb new file mode 100644 index 00000000000..c4bbc231464 --- /dev/null +++ b/lib/gitlab/slash_commands/presenters/run.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Gitlab + module SlashCommands + module Presenters + class Run < Presenters::Base + # rubocop: disable CodeReuse/ActiveRecord + def present(pipeline) + build = pipeline.builds.take + + if build && (responder = Chat::Responder.responder_for(build)) + in_channel_response(responder.scheduled_output) + else + unsupported_chat_service + end + end + # rubocop: enable CodeReuse/ActiveRecord + + def unsupported_chat_service + ephemeral_response(text: 'Sorry, this chat service is currently not supported by GitLab ChatOps.') + end + + def failed_to_schedule(command) + ephemeral_response( + text: 'The command could not be scheduled. Make sure that your ' \ + 'project has a .gitlab-ci.yml that defines a job with the ' \ + "name #{command.inspect}" + ) + end + end + end + end +end diff --git a/lib/gitlab/slash_commands/run.rb b/lib/gitlab/slash_commands/run.rb new file mode 100644 index 00000000000..10a545e28ac --- /dev/null +++ b/lib/gitlab/slash_commands/run.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Gitlab + module SlashCommands + # Slash command for triggering chatops jobs. + class Run < BaseCommand + def self.match(text) + /\Arun\s+(?<command>\S+)(\s+(?<arguments>.+))?\z/.match(text) + end + + def self.help_message + 'run <command> <arguments>' + end + + def self.available?(project) + Chat.available? && project.builds_enabled? + end + + def self.allowed?(project, user) + can?(user, :create_pipeline, project) + end + + def execute(match) + command = Chat::Command.new( + project: project, + chat_name: chat_name, + name: match[:command], + arguments: match[:arguments], + channel: params[:channel_id], + response_url: params[:response_url] + ) + + presenter = Gitlab::SlashCommands::Presenters::Run.new + pipeline = command.try_create_pipeline + + if pipeline&.persisted? + presenter.present(pipeline) + else + presenter.failed_to_schedule(command.name) + end + end + end + end +end diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb index 0d9b0be1c8e..29517591c51 100644 --- a/lib/gitlab/tracing.rb +++ b/lib/gitlab/tracing.rb @@ -27,10 +27,11 @@ module Gitlab def self.tracing_url return unless tracing_url_enabled? - tracing_url_template % { - correlation_id: Gitlab::CorrelationId.current_id.to_s, - service: Gitlab.process_name - } + # Avoid using `format` since it can throw TypeErrors + # which we want to avoid on unsanitised env var input + tracing_url_template.to_s + .gsub(/\{\{\s*correlation_id\s*\}\}/, Gitlab::CorrelationId.current_id.to_s) + .gsub(/\{\{\s*service\s*\}\}/, Gitlab.process_name) end end end diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb index 4187014d49e..49ec196b103 100644 --- a/lib/sentry/client.rb +++ b/lib/sentry/client.rb @@ -54,7 +54,7 @@ module Sentry def handle_response(response) unless response.code == 200 - raise Client::Error, "Sentry response error: #{response.code}" + raise Client::Error, "Sentry response status code: #{response.code}" end response.as_json diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 973950d27aa..0d47deab951 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -285,6 +285,21 @@ msgstr "" msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need." msgstr "" +msgid "A .NET Core console application template, customizable for any .NET Core project" +msgstr "" + +msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features." +msgstr "" + +msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features." +msgstr "" + +msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features." +msgstr "" + +msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features." +msgstr "" + msgid "A collection of graphs regarding Continuous Integration" msgstr "" @@ -300,6 +315,9 @@ msgstr "" msgid "A new branch will be created in your fork and a new merge request will be started." msgstr "" +msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features." +msgstr "" + msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}." msgstr "" @@ -951,6 +969,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Background Color" +msgstr "" + msgid "Background Jobs" msgstr "" @@ -1542,9 +1563,6 @@ msgstr "" msgid "Closed" msgstr "" -msgid "Closed (moved)" -msgstr "" - msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}." msgstr "" @@ -2453,6 +2471,9 @@ msgstr "" msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}." msgstr "" +msgid "Customize colors" +msgstr "" + msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import." msgstr "" @@ -3295,7 +3316,7 @@ msgstr "" msgid "Failed to load emoji list." msgstr "" -msgid "Failed to load errors from Sentry" +msgid "Failed to load errors from Sentry. Error message: %{errorMessage}" msgstr "" msgid "Failed to remove issue from board, please try again." @@ -3435,6 +3456,12 @@ msgstr "" msgid "Follow the steps below to export your Google Code project data." msgstr "" +msgid "Font Color" +msgstr "" + +msgid "Footer message" +msgstr "" + msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)" msgstr "" @@ -3762,6 +3789,9 @@ msgstr "" msgid "GroupsTree|Search by name" msgstr "" +msgid "Header message" +msgstr "" + msgid "Health Check" msgstr "" @@ -4097,6 +4127,9 @@ msgstr "" msgid "Invoke Time" msgstr "" +msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})" +msgstr "" + msgid "Issue" msgstr "" @@ -4115,7 +4148,7 @@ msgstr "" msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." msgstr "" -msgid "Issues, merge requests, pushes and comments." +msgid "Issues, merge requests, pushes, and comments." msgstr "" msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected." @@ -4271,6 +4304,9 @@ msgstr "" msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed." msgstr "" +msgid "Language" +msgstr "" + msgid "Large File Storage" msgstr "" @@ -4844,9 +4880,6 @@ msgstr "" msgid "New Snippet" msgstr "" -msgid "New Snippets" -msgstr "" - msgid "New branch" msgstr "" @@ -5194,6 +5227,9 @@ msgstr "" msgid "Operations Dashboard" msgstr "" +msgid "Operations Settings" +msgstr "" + msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab." msgstr "" @@ -5530,6 +5566,9 @@ msgstr "" msgid "Preferences|Navigation theme" msgstr "" +msgid "Preferences|This feature is experimental and translations are not complete yet" +msgstr "" + msgid "Press Enter or click to search" msgstr "" @@ -5722,9 +5761,6 @@ msgstr "" msgid "Profiles|This emoji and message will appear on your profile and throughout the interface." msgstr "" -msgid "Profiles|This feature is experimental and translations are not complete yet" -msgstr "" - msgid "Profiles|This information will appear on your profile" msgstr "" @@ -6710,9 +6746,15 @@ msgstr "" msgid "Sherlock Transactions" msgstr "" +msgid "Show all activity" +msgstr "" + msgid "Show command" msgstr "" +msgid "Show comments only" +msgstr "" + msgid "Show complete raw log" msgstr "" @@ -6772,6 +6814,24 @@ msgstr "" msgid "Snippets" msgstr "" +msgid "SnippetsEmptyState|Explore public snippets" +msgstr "" + +msgid "SnippetsEmptyState|New snippet" +msgstr "" + +msgid "SnippetsEmptyState|No snippets found" +msgstr "" + +msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep." +msgstr "" + +msgid "SnippetsEmptyState|There are no snippets to show." +msgstr "" + +msgid "SnippetsEmptyState|They can be either public or private." +msgstr "" + msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again." msgstr "" @@ -7048,6 +7108,9 @@ msgstr "" msgid "Starts at (UTC)" msgstr "" +msgid "State your message to activate" +msgstr "" + msgid "Status" msgstr "" @@ -7129,6 +7192,9 @@ msgstr "" msgid "System default (%{default})" msgstr "" +msgid "System header and footer" +msgstr "" + msgid "System metrics (Custom)" msgstr "" @@ -8597,6 +8663,9 @@ msgstr "" msgid "You'll need to use different branch names to get a valid comparison." msgstr "" +msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options." +msgstr "" + msgid "You're receiving this email because %{reason}." msgstr "" diff --git a/package.json b/package.json index 9df2a3ccff7..3d96f8b80d9 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@babel/plugin-syntax-import-meta": "^7.2.0", "@babel/preset-env": "^7.3.1", "@gitlab/csslab": "^1.8.0", - "@gitlab/svgs": "^1.52.0", + "@gitlab/svgs": "^1.54.0", "@gitlab/ui": "^2.0.4", "apollo-boost": "^0.1.20", "apollo-client": "^2.4.5", @@ -72,6 +72,7 @@ "graphql": "^14.0.2", "imports-loader": "^0.8.0", "jed": "^1.1.1", + "jest-transform-graphql": "^2.1.0", "jquery": "^3.2.1", "jquery-ujs": "1.2.2", "jquery.waitforimages": "^2.2.0", diff --git a/qa/Gemfile b/qa/Gemfile index 873eac1013f..f29006617ed 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -8,3 +8,4 @@ gem 'rspec', '~> 3.7' gem 'selenium-webdriver', '~> 3.12' gem 'airborne', '~> 0.2.13' gem 'nokogiri', '~> 1.10.1' +gem 'rspec-retry', '~> 0.6.1' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 9f84bdc3828..c3d9f558c23 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -76,6 +76,8 @@ GEM rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) + rspec-retry (0.6.1) + rspec-core (> 3.3) rspec-support (3.7.0) rubyzip (1.2.2) selenium-webdriver (3.141.0) @@ -101,6 +103,7 @@ DEPENDENCIES pry-byebug (~> 3.5.1) rake (~> 12.3.0) rspec (~> 3.7) + rspec-retry (~> 0.6.1) selenium-webdriver (~> 3.12) BUNDLED WITH diff --git a/qa/README.md b/qa/README.md index 5e32496ea9f..7709db36f8e 100644 --- a/qa/README.md +++ b/qa/README.md @@ -1,21 +1,25 @@ -# GitLab QA - Integration tests for GitLab +# GitLab QA - End-to-end tests for GitLab -This directory contains integration tests for GitLab. +This directory contains [end-to-end tests](doc/development/testing_guide/end_to_end_tests.md) +for GitLab. It includes the test framework and the tests themselves. + +The tests can be found in `qa/specs/features` (not to be confused with the unit +tests for the test framework, which are in `spec/`). It is part of the [GitLab QA project](https://gitlab.com/gitlab-org/gitlab-qa). ## What is it? -GitLab QA is an integration tests suite for GitLab. +GitLab QA is an end-to-end tests suite for GitLab. -These are black-box and entirely click-driven integration tests you can run +These are black-box and entirely click-driven end-to-end tests you can run against any existing instance. ## How does it work? 1. When we release a new version of GitLab, we build a Docker images for it. 1. Along with GitLab Docker Images we also build and publish GitLab QA images. -1. GitLab QA project uses these images to execute integration tests. +1. GitLab QA project uses these images to execute end-to-end tests. ## Validating GitLab views / partials / selectors in merge requests @@ -260,6 +260,10 @@ module QA autoload :Sidebar, 'qa/page/issuable/sidebar' end + module Alert + autoload :AutoDevopsAlert, 'qa/page/alert/auto_devops_alert' + end + module Layout autoload :Banner, 'qa/page/layout/banner' end diff --git a/qa/qa/page/alert/auto_devops_alert.rb b/qa/qa/page/alert/auto_devops_alert.rb new file mode 100644 index 00000000000..8f66c805b77 --- /dev/null +++ b/qa/qa/page/alert/auto_devops_alert.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Page + module Alert + class AutoDevopsAlert < Page::Base + view 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml' do + element :auto_devops_banner + end + end + end + end +end diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 49c676c01f2..9c218f4ed8b 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -4,10 +4,6 @@ module QA::Page COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running PASSED_STATUS = 'passed'.freeze - view 'app/assets/javascripts/jobs/components/job_app.vue' do - element :loading_animation - end - view 'app/assets/javascripts/jobs/components/job_log.vue' do element :build_trace end @@ -20,22 +16,13 @@ module QA::Page element :pipeline_path end - def completed? - COMPLETED_STATUSES.include?(status_badge) - end - def successful?(timeout: 60) - wait(reload: false, max: timeout) do - completed? && !trace_loading? - end + raise "Timed out waiting for the build trace to load" unless loaded? + raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout) status_badge == PASSED_STATUS end - def trace_loading? - has_element?(:loading_animation) - end - # Reminder: You may wish to wait for a particular job status before checking output def output find_element(:build_trace).text @@ -43,6 +30,16 @@ module QA::Page private + def loaded?(wait: 60) + has_element?(:build_trace, wait: wait) + end + + def completed?(timeout: 60) + wait(reload: false, max: timeout) do + COMPLETED_STATUSES.include?(status_badge) + end + end + def status_badge find_element(:status_badge).text end diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb index 98ac5c32d91..d4e1679b6bf 100644 --- a/qa/qa/page/project/operations/kubernetes/show.rb +++ b/qa/qa/page/project/operations/kubernetes/show.rb @@ -14,6 +14,11 @@ module QA element :ingress_ip_address, 'id="ingress-ip-address"' # rubocop:disable QA/ElementWithPattern end + view 'app/views/clusters/clusters/_form.html.haml' do + element :base_domain + element :save_domain + end + def install!(application_name) within(".js-cluster-application-row-#{application_name}") do page.has_button?('Install', wait: 30) @@ -32,6 +37,14 @@ module QA # ip address is assigned for the ingress controller page.find('#ingress-ip-address', wait: 1200).value end + + def set_domain(domain) + fill_element :base_domain, domain + end + + def save_domain + click_element :save_domain + end end end end diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index f192f1fc64b..22b0a168964 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -18,6 +18,10 @@ module QA::Page element :status_icon, 'ci-status-icon-${status}' # rubocop:disable QA/ElementWithPattern end + view 'app/views/projects/pipelines/_info.html.haml' do + element :pipeline_badges + end + def running? within('.ci-header-container') do page.has_content?('running') @@ -32,6 +36,12 @@ module QA::Page end end + def has_tag?(tag_name) + within_element(:pipeline_badges) do + has_selector?('.badge', text: tag_name) + end + end + def go_to_job(job_name) find_element(:job_link, job_name).click end diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb index 986b31da528..93a06be6818 100644 --- a/qa/qa/resource/kubernetes_cluster.rb +++ b/qa/qa/resource/kubernetes_cluster.rb @@ -12,10 +12,6 @@ module QA Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip) end - attribute :domain do - "#{ingress_ip}.nip.io" - end - def fabricate! @project.visit! @@ -53,6 +49,12 @@ module QA page.await_installed(:ingress) if @install_ingress page.await_installed(:prometheus) if @install_prometheus page.await_installed(:runner) if @install_runner + + if @install_ingress + populate(:ingress_ip) + page.set_domain("#{ingress_ip}.nip.io") + page.save_domain + end end end end diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb index 704c65467e0..9d7c1aea508 100644 --- a/qa/qa/runtime/namespace.rb +++ b/qa/qa/runtime/namespace.rb @@ -8,7 +8,9 @@ module QA end def name - Runtime::Env.namespace_name || "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}" + # If any changes are made to the name tag, following script has to be considered: + # https://ops.gitlab.net/gitlab-com/gl-infra/traffic-generator/blob/master/bin/janitor.bash + @name ||= Runtime::Env.namespace_name || "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}-#{SecureRandom.hex(8)}" end def path diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb index 60836a7b98d..f146636c49a 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb @@ -2,7 +2,7 @@ module QA # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/31 - context 'Create', :quarantine do + context 'Create' do describe 'Merge request squashing' do it 'user squashes commits while merging' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb index 2e4915b898d..99601e3d230 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true module QA - context 'Create' do + # https://gitlab.com/gitlab-org/quality/staging/issues/40 + context 'Create', :quarantine do describe 'Push mirror a repository over HTTP' do it 'configures and syncs a (push) mirrored repository' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb index 7d96da32423..243f0b83b77 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true module QA - context 'Create' do + # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/37 + context 'Create', :quarantine do describe 'push after setting the file size limit via admin/application_settings' do before(:all) do push = Resource::Repository::ProjectPush.fabricate! do |p| diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 2375aa4ce91..3f65eabc756 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -95,7 +95,7 @@ module QA Page::Project::Pipeline::Show.perform(&:go_to_first_job) Page::Project::Job::Show.perform do |job| - expect(job).to be_successful, "Job status did not become \"passed\"." + expect(job).to be_successful expect(job.output).to include(sha1sum) end end diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 8cd353fa250..02bd378acfc 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -4,13 +4,13 @@ require 'pathname' module QA # Transient failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/68 - context 'Configure', :orchestrated, :kubernetes, :quarantine do - describe 'Auto DevOps support' do - def login - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - end + context 'Configure' do + def login + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + end + describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do [true, false].each do |rbac| context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do before(:all) do @@ -40,7 +40,7 @@ module QA # Create and connect K8s cluster @cluster = Service::KubernetesCluster.new(rbac: rbac).create! - kubernetes_cluster = Resource::KubernetesCluster.fabricate! do |cluster| + Resource::KubernetesCluster.fabricate! do |cluster| cluster.project = @project cluster.cluster = @cluster cluster.install_helm_tiller = true @@ -49,14 +49,11 @@ module QA cluster.install_runner = true end - kubernetes_cluster.populate(:ingress_ip) @project.visit! Page::Project::Menu.perform(&:click_ci_cd_settings) Page::Project::Settings::CICD.perform do |p| p.enable_auto_devops end - - kubernetes_cluster.populate(:domain) end after(:all) do @@ -76,7 +73,7 @@ module QA pipeline.go_to_job('build') end Page::Project::Job::Show.perform do |job| - expect(job).to be_sucessful(timeout: 600), "Job did not pass" + expect(job).to be_successful(timeout: 600) job.click_element(:pipeline_path) end @@ -85,7 +82,7 @@ module QA pipeline.go_to_job('test') end Page::Project::Job::Show.perform do |job| - expect(job).to be_sucessful(timeout: 600), "Job did not pass" + expect(job).to be_successful(timeout: 600) job.click_element(:pipeline_path) end @@ -94,7 +91,7 @@ module QA pipeline.go_to_job('production') end Page::Project::Job::Show.perform do |job| - expect(job).to be_sucessful(timeout: 1200), "Job did not pass" + expect(job).to be_successful(timeout: 1200) job.click_element(:pipeline_path) end @@ -137,7 +134,7 @@ module QA pipeline.go_to_job('build') end Page::Project::Job::Show.perform do |job| - expect(job).to be_sucessful(timeout: 600), "Job did not pass" + expect(job).to be_successful(timeout: 600) job.click_element(:pipeline_path) end @@ -146,7 +143,7 @@ module QA pipeline.go_to_job('test') end Page::Project::Job::Show.perform do |job| - expect(job).to be_sucessful(timeout: 600), "Job did not pass" + expect(job).to be_successful(timeout: 600) job.click_element(:pipeline_path) end @@ -155,7 +152,7 @@ module QA pipeline.go_to_job('production') end Page::Project::Job::Show.perform do |job| - expect(job).to be_sucessful(timeout: 1200), "Job did not pass" + expect(job).to be_successful(timeout: 1200) job.click_element(:pipeline_path) end @@ -176,5 +173,38 @@ module QA end end end + + describe 'Auto DevOps', :smoke do + it 'enables AutoDevOps by default' do + login + + project = Resource::Project.fabricate! do |p| + p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops' + p.description = 'Project with AutoDevOps' + end + + project.visit! + + Page::Alert::AutoDevopsAlert.perform do |alert| + expect(alert).to have_text(/.*The Auto DevOps pipeline has been enabled.*/) + end + + # Create AutoDevOps repo + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.directory = Pathname + .new(__dir__) + .join('../../../../../fixtures/auto_devops_rack') + push.commit_message = 'Create AutoDevOps compatible Project' + end + + Page::Project::Menu.perform(&:click_ci_cd_pipelines) + Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline) + + Page::Project::Pipeline::Show.perform do |pipeline| + expect(pipeline).to have_tag('Auto DevOps') + end + end + end end end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index 20a153f3f63..cbdd6e881b1 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -1,4 +1,5 @@ require_relative '../qa' +require 'rspec/retry' %w[helpers shared_examples].each do |d| Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f } @@ -31,6 +32,17 @@ RSpec.configure do |config| config.profile_examples = 10 config.order = :random Kernel.srand config.seed + + # show retry status in spec process + config.verbose_retry = true + + # show exception that triggers a retry if verbose_retry is set to true + config.display_try_failure_messages = true + + config.around do |example| + retry_times = example.metadata.keys.include?(:quarantine) ? 1 : 3 + example.run_with_retry retry: retry_times + end end # Skip tests in quarantine unless we explicitly focus on them. diff --git a/qa/spec/spec_helper_spec.rb b/qa/spec/spec_helper_spec.rb index 2427999e110..27ec1ec80fe 100644 --- a/qa/spec/spec_helper_spec.rb +++ b/qa/spec/spec_helper_spec.rb @@ -28,6 +28,22 @@ describe 'rspec config tests' do end end + let(:group_2) do + RSpec.describe do + before(:all) do + @expectations = [1, 2, 3] + end + + example 'not in quarantine' do + expect(@expectations.shift).to be(3) + end + + example 'in quarantine', :quarantine do + expect(@expectations.shift).to be(3) + end + end + end + context 'with no tags focussed' do before do group.run @@ -301,4 +317,39 @@ describe 'rspec config tests' do end end end + + context 'rspec retry' do + context 'in an untagged context' do + before do + group_2.run + end + + it 'should run example :retry times' do + examples = group_2.descendant_filtered_examples + ex = examples.find { |e| e.description == 'not in quarantine' } + expect(ex.execution_result.status).to eq(:passed) + end + end + + context 'with :quarantine focussed' do + before do + RSpec.configure do |config| + config.inclusion_filter = :quarantine + end + group_2.run + end + + after do + RSpec.configure do |config| + config.inclusion_filter.clear + end + end + + it 'should run example once only' do + examples = group_2.descendant_filtered_examples + ex = examples.find { |e| e.description == 'in quarantine' } + expect(ex.execution_result.status).to eq(:failed) + end + end + end end diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index 848364b4a9b..bc73225c1bf 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -35,7 +35,7 @@ fi # Do not use 'README.md', instead use 'index.md' # Number of 'README.md's as of 2018-03-26 -NUMBER_READMES_CE=42 +NUMBER_READMES_CE=43 NUMBER_READMES_EE=46 FIND_READMES=$(find doc/ -name "README.md" | wc -l) echo '=> Checking for new README.md files...' diff --git a/spec/controllers/admin/appearances_controller_spec.rb b/spec/controllers/admin/appearances_controller_spec.rb new file mode 100644 index 00000000000..4ddd0953267 --- /dev/null +++ b/spec/controllers/admin/appearances_controller_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Admin::AppearancesController do + let(:admin) { create(:admin) } + let(:header_message) { "Header message" } + let(:footer_message) { "Footer" } + + describe 'POST #create' do + let(:create_params) do + { + title: "Foo", + description: "Bar", + header_message: header_message, + footer_message: footer_message + } + end + + before do + sign_in(admin) + end + + it 'creates appearance with footer and header message' do + post :create, params: { appearance: create_params } + + expect(Appearance.current).to have_attributes( + header_message: header_message, + footer_message: footer_message + ) + end + end + + describe 'PUT #update' do + let(:update_params) do + { + header_message: header_message, + footer_message: footer_message + } + end + + before do + create(:appearance) + + sign_in(admin) + end + + it 'updates appearance with footer and header message' do + put :update, params: { appearance: update_params } + + expect(Appearance.current).to have_attributes( + header_message: header_message, + footer_message: footer_message + ) + end + end +end diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb index 4cf14030ca1..82e24213408 100644 --- a/spec/controllers/admin/runners_controller_spec.rb +++ b/spec/controllers/admin/runners_controller_spec.rb @@ -1,18 +1,35 @@ require 'spec_helper' describe Admin::RunnersController do - let(:runner) { create(:ci_runner) } + let!(:runner) { create(:ci_runner) } before do sign_in(create(:admin)) end describe '#index' do + render_views + it 'lists all runners' do get :index expect(response).to have_gitlab_http_status(200) end + + it 'avoids N+1 queries', :request_store do + get :index + + control_count = ActiveRecord::QueryRecorder.new { get :index }.count + + create(:ci_runner, :tagged_only) + + # There is still an N+1 query for `runner.builds.count` + expect { get :index }.not_to exceed_query_limit(control_count + 1) + + expect(response).to have_gitlab_http_status(200) + expect(response.body).to have_content('tag1') + expect(response.body).to have_content('tag2') + end end describe '#show' do diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index c934db9e237..cb24a6ef142 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -8,6 +8,20 @@ describe Admin::UsersController do sign_in(admin) end + describe 'GET #index' do + it 'retrieves all users' do + get :index + + expect(assigns(:users)).to match_array([user, admin]) + end + + it 'filters by admins' do + get :index, params: { filter: 'admins' } + + expect(assigns(:users)).to eq([admin]) + end + end + describe 'GET :id' do it 'finds a user case-insensitively' do user = create(:user, username: 'CaseSensitive') diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index c9e520317e8..dca74bd5f84 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -665,6 +665,14 @@ describe ApplicationController do expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate, no-store' end + + it 'does not set the "no-store" header for XHR requests' do + sign_in(user) + + get :index, xhr: true + + expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate' + end end end end diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb index 307c5d60c57..8580900215c 100644 --- a/spec/controllers/concerns/issuable_collections_spec.rb +++ b/spec/controllers/concerns/issuable_collections_spec.rb @@ -112,7 +112,8 @@ describe IssuableCollections do assignee_username: 'user1', author_id: '2', author_username: 'user2', - authorized_only: 'true', + authorized_only: 'yes', + confidential: true, due_date: '2017-01-01', group_id: '3', iids: '4', @@ -140,6 +141,7 @@ describe IssuableCollections do 'assignee_username' => 'user1', 'author_id' => '2', 'author_username' => 'user2', + 'confidential' => true, 'label_name' => 'foo', 'milestone_title' => 'bar', 'my_reaction_emoji' => 'thumbsup', diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb index 760c0fab130..ee881f85233 100644 --- a/spec/controllers/profiles/preferences_controller_spec.rb +++ b/spec/controllers/profiles/preferences_controller_spec.rb @@ -43,7 +43,8 @@ describe Profiles::PreferencesController do color_scheme_id: '1', dashboard: 'stars', theme_id: '2', - first_day_of_week: '1' + first_day_of_week: '1', + preferred_language: 'jp' }.with_indifferent_access expect(user).to receive(:assign_attributes).with(ActionController::Parameters.new(prefs).permit!) diff --git a/spec/controllers/projects/autocomplete_sources_controller_spec.rb b/spec/controllers/projects/autocomplete_sources_controller_spec.rb new file mode 100644 index 00000000000..4bc72042710 --- /dev/null +++ b/spec/controllers/projects/autocomplete_sources_controller_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::AutocompleteSourcesController do + set(:group) { create(:group) } + set(:project) { create(:project, namespace: group) } + set(:issue) { create(:issue, project: project) } + set(:user) { create(:user) } + + describe 'GET members' do + before do + group.add_owner(user) + sign_in(user) + end + + it 'returns an array of member object' do + get :members, format: :json, params: { namespace_id: group.path, project_id: project.path, type: issue.class.name, type_id: issue.id } + + all = json_response.find {|member| member["username"] == 'all'} + the_group = json_response.find {|member| member["username"] == group.full_path} + the_user = json_response.find {|member| member["username"] == user.username} + + expect(all.symbolize_keys).to include(username: 'all', + name: 'All Project and Group Members', + count: 1) + + expect(the_group.symbolize_keys).to include(type: group.class.name, + name: group.full_name, + avatar_url: group.avatar_url, + count: 1) + + expect(the_user.symbolize_keys).to include(type: user.class.name, + name: user.name, + avatar_url: user.avatar_url) + end + end +end diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index aa97a417a98..36ce1119100 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -54,9 +54,9 @@ describe Projects::EnvironmentsController do it 'responds with a flat payload describing available environments' do expect(environments.count).to eq 3 - expect(environments.first['name']).to eq 'production' - expect(environments.second['name']).to eq 'staging/review-1' - expect(environments.third['name']).to eq 'staging/review-2' + expect(environments.first).to include('name' => 'production', 'name_without_type' => 'production') + expect(environments.second).to include('name' => 'staging/review-1', 'name_without_type' => 'review-1') + expect(environments.third).to include('name' => 'staging/review-2', 'name_without_type' => 'review-2') expect(json_response['available_count']).to eq 3 expect(json_response['stopped_count']).to eq 1 end @@ -155,9 +155,9 @@ describe Projects::EnvironmentsController do expect(response).to be_ok expect(response).not_to render_template 'folder' expect(json_response['environments'][0]) - .to include('name' => 'staging-1.0/review') + .to include('name' => 'staging-1.0/review', 'name_without_type' => 'review') expect(json_response['environments'][1]) - .to include('name' => 'staging-1.0/zzz') + .to include('name' => 'staging-1.0/zzz', 'name_without_type' => 'zzz') end end end diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index 73fb7307e11..8decd8f1382 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -24,4 +24,20 @@ describe Projects::GraphsController do expect(response).to redirect_to action: :charts end end + + describe 'charts' do + context 'when languages were previously detected' do + let!(:repository_language) { create(:repository_language, project: project) } + + it 'sets the languages properly' do + get(:charts, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' }) + + expect(assigns[:languages]).to eq( + [value: repository_language.share, + label: repository_language.name, + color: repository_language.color, + highlight: repository_language.color]) + end + end + end end diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 81892575889..0b0f5117784 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -252,8 +252,8 @@ describe Projects::NotesController do note: 'some note', noteable_id: merge_request.id.to_s, noteable_type: 'MergeRequest', - merge_request_diff_head_sha: 'sha', - in_reply_to_discussion_id: nil + commit_id: nil, + merge_request_diff_head_sha: 'sha' }).permit! expect(Notes::CreateService).to receive(:new).with(project, user, service_params).and_return(double(execute: true)) @@ -266,6 +266,22 @@ describe Projects::NotesController do end end + context 'when creating a comment on a commit with SHA1 starting with a large number' do + let(:commit) { create(:commit, project: project, id: '842616594688d2351480dfebd67b3d8d15571e6d') } + + it 'creates a note successfully' do + expect do + post :create, params: { + note: { note: 'some note', commit_id: commit.id }, + namespace_id: project.namespace, + project_id: project, + target_type: 'commit', + target_id: commit.id + } + end.to change { Note.count }.by(1) + end + end + context 'when creating a commit comment from an MR fork' do let(:project) { create(:project, :repository) } diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb index 4b742a5d427..d6eece47804 100644 --- a/spec/controllers/projects/pages_controller_spec.rb +++ b/spec/controllers/projects/pages_controller_spec.rb @@ -42,6 +42,18 @@ describe Projects::PagesController do expect(response).to have_gitlab_http_status(302) end + + context 'when user is developer' do + before do + project.add_developer(user) + end + + it 'returns 404 status' do + delete :destroy, params: request_params + + expect(response).to have_gitlab_http_status(404) + end + end end context 'pages disabled' do diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb index 1b12f84d7b8..e7fd22a96b2 100644 --- a/spec/factories/personal_access_tokens.rb +++ b/spec/factories/personal_access_tokens.rb @@ -1,13 +1,14 @@ FactoryBot.define do factory :personal_access_token do user - token { SecureRandom.hex(50) } sequence(:name) { |n| "PAT #{n}" } revoked false expires_at { 5.days.from_now } scopes ['api'] impersonation false + after(:build) { |personal_access_token| personal_access_token.ensure_token } + trait :impersonation do impersonation true end @@ -21,7 +22,7 @@ FactoryBot.define do end trait :invalid do - token nil + token_digest nil end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index f7ef34d773b..30d3b22d868 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -313,6 +313,20 @@ FactoryBot.define do end end + factory :youtrack_project, parent: :project do + has_external_issue_tracker true + + after :create do |project| + project.create_youtrack_service( + active: true, + properties: { + 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack', + 'issues_url' => 'http://youtrack/issues/:id' + } + ) + end + end + factory :jira_project, parent: :project do has_external_issue_tracker true jira_service diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 57215c0d1e9..83cd686818c 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -39,6 +39,38 @@ describe 'Admin Appearance' do expect_custom_new_project_appearance(appearance) end + context 'Custom system header and footer' do + before do + sign_in(create(:admin)) + end + + context 'when system header and footer messages are empty' do + it 'shows custom system header and footer fields' do + visit admin_appearances_path + + expect(page).to have_field('appearance_header_message', with: '') + expect(page).to have_field('appearance_footer_message', with: '') + expect(page).to have_field('appearance_message_background_color') + expect(page).to have_field('appearance_message_font_color') + end + end + + context 'when system header and footer messages are not empty' do + before do + appearance.update(header_message: 'Foo', footer_message: 'Bar') + end + + it 'shows custom system header and footer fields' do + visit admin_appearances_path + + expect(page).to have_field('appearance_header_message', with: appearance.header_message) + expect(page).to have_field('appearance_footer_message', with: appearance.footer_message) + expect(page).to have_field('appearance_message_background_color') + expect(page).to have_field('appearance_message_font_color') + end + end + end + it 'Custom sign-in page' do visit new_user_session_path diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index cbddf117462..55f5ff04d01 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -44,7 +44,7 @@ describe 'Dashboard shortcuts', :js do find('body').send_keys([:shift, 'S']) find('.nothing-here-block') - expect(page).to have_selector('.snippets-list-holder') + expect(page).to have_content('No snippets found') find('body').send_keys([:shift, 'P']) diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb index fb4263d74c4..0e248c8732d 100644 --- a/spec/features/dashboard/snippets_spec.rb +++ b/spec/features/dashboard/snippets_spec.rb @@ -13,6 +13,21 @@ describe 'Dashboard snippets' do it_behaves_like 'paginated snippets' end + context 'when there are no project snippets', :js do + let(:project) { create(:project, :public) } + before do + sign_in(project.owner) + visit dashboard_snippets_path + end + + it 'shows the empty state when there are no snippets' do + element = page.find('.row.empty-state') + + expect(element).to have_content("Snippets are small pieces of code or notes that you want to keep.") + expect(element.find('.svg-content img')['src']).to have_content('illustrations/snippets_empty') + end + end + context 'filtering by visibility' do let(:user) { create(:user) } let!(:snippets) do diff --git a/spec/features/display_system_header_and_footer_bar_spec.rb b/spec/features/display_system_header_and_footer_bar_spec.rb new file mode 100644 index 00000000000..af9d9a5834f --- /dev/null +++ b/spec/features/display_system_header_and_footer_bar_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Display system header and footer bar' do + let(:header_message) { "Foo" } + let(:footer_message) { "Bar" } + + shared_examples 'system header is configured' do + it 'shows system header' do + expect(page).to have_css('.header-message') + end + + it 'shows the correct content' do + page.within('.header-message') do + expect(page).to have_content(header_message) + end + end + end + + shared_examples 'system footer is configured' do + it 'shows system footer' do + expect(page).to have_css('.footer-message') + end + + it 'shows the correct content' do + page.within('.footer-message') do + expect(page).to have_content(footer_message) + end + end + end + + shared_examples 'system header is not configured' do + it 'does not show system header' do + expect(page).not_to have_css('.header-message') + end + end + + shared_examples 'system footer is not configured' do + it 'does not show system footer' do + expect(page).not_to have_css('.footer-message') + end + end + + context 'when authenticated' do + context 'when system header and footer are not configured' do + before do + sign_in(create(:user)) + + visit root_path + end + + it_behaves_like 'system header is not configured' + it_behaves_like 'system footer is not configured' + end + + context 'when only system header is defined' do + before do + create(:appearance, header_message: header_message) + + sign_in(create(:user)) + visit root_path + end + + it_behaves_like 'system header is configured' + it_behaves_like 'system footer is not configured' + end + + context 'when only system footer is defined' do + before do + create(:appearance, footer_message: footer_message) + + sign_in(create(:user)) + visit root_path + end + + it_behaves_like 'system header is not configured' + it_behaves_like 'system footer is configured' + end + + context 'when system header and footer are defined' do + before do + create(:appearance, header_message: header_message, footer_message: footer_message) + + sign_in(create(:user)) + visit root_path + end + + it_behaves_like 'system header is configured' + it_behaves_like 'system footer is configured' + end + end + + context 'when not authenticated' do + context 'when system header and footer are not configured' do + before do + visit root_path + end + + it_behaves_like 'system header is not configured' + it_behaves_like 'system footer is not configured' + end + + context 'when only system header is defined' do + before do + create(:appearance, header_message: header_message) + + visit root_path + end + + it_behaves_like 'system header is configured' + it_behaves_like 'system footer is not configured' + end + + context 'when only system footer is defined' do + before do + create(:appearance, footer_message: footer_message) + + visit root_path + end + + it_behaves_like 'system header is not configured' + it_behaves_like 'system footer is configured' + end + + context 'when system header and footer are defined' do + before do + create(:appearance, header_message: header_message, footer_message: footer_message) + + visit root_path + end + + it_behaves_like 'system header is configured' + it_behaves_like 'system footer is configured' + end + end +end diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index 2f45ef856a5..7b6e9cd66b2 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -28,6 +28,22 @@ describe 'issuable list' do expect(first('.fa-thumbs-down').find(:xpath, '..')).to have_content(1) expect(first('.fa-comments').find(:xpath, '..')).to have_content(2) end + + it 'sorts labels alphabetically' do + label1 = create(:label, project: project, title: 'a') + label2 = create(:label, project: project, title: 'z') + label3 = create(:label, project: project, title: 'X') + label4 = create(:label, project: project, title: 'B') + issuable = create_issuable(issuable_type) + issuable.labels << [label1, label2, label3, label4] + + visit_issuable_list(issuable_type) + + expect(all('.label-link')[0].text).to have_content('B') + expect(all('.label-link')[1].text).to have_content('X') + expect(all('.label-link')[2].text).to have_content('a') + expect(all('.label-link')[3].text).to have_content('z') + end end it "counts merge requests closing issues icons for each issue" do @@ -45,6 +61,14 @@ describe 'issuable list' do end end + def create_issuable(issuable_type) + if issuable_type == :issue + create(:issue, project: project) + else + create(:merge_request, source_project: project) + end + end + def create_issuables(issuable_type) 3.times do |n| issuable = diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb index 0e296ab2109..096756f19cc 100644 --- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb @@ -66,7 +66,7 @@ describe 'Dropdown hint', :js do it 'filters with text' do filtered_search.set('a') - expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 4) + expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5) end end @@ -119,6 +119,15 @@ describe 'Dropdown hint', :js do expect_tokens([{ name: 'my-reaction' }]) expect_filtered_search_input_empty end + + it 'opens the yes-no dropdown when you click on confidential' do + click_hint('confidential') + + expect(page).to have_css(js_dropdown_hint, visible: false) + expect(page).to have_css('#js-dropdown-confidential', visible: true) + expect_tokens([{ name: 'confidential' }]) + expect_filtered_search_input_empty + end end describe 'selecting from dropdown with some input' do diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb index 8abab3f35d6..da23aea1fc9 100644 --- a/spec/features/issues/filtered_search/search_bar_spec.rb +++ b/spec/features/issues/filtered_search/search_bar_spec.rb @@ -86,7 +86,7 @@ describe 'Search bar', :js do expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: original_size) end - it 'resets the dropdown filters' do + it 'resets the dropdown filters', :quarantine do filtered_search.click hint_offset = get_left_style(find('#js-dropdown-hint')['style']) @@ -100,7 +100,7 @@ describe 'Search bar', :js do find('.filtered-search-box .clear-search').click filtered_search.click - expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5) + expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6) expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset) end end diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index a4c34ce85f0..9fd661d80ae 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -59,13 +59,6 @@ describe 'Visual tokens', :js do expect(page).to have_css('#js-dropdown-author', visible: false) end - it 'ends editing mode when scroll container is clicked' do - find('.scroll-container').click - - expect_filtered_search_input_empty - expect(page).to have_css('#js-dropdown-author', visible: false) - end - describe 'selecting different author from dropdown' do before do filter_author_dropdown.find('.filter-dropdown-item .dropdown-light-content', text: "@#{user_rock.username}").click @@ -109,13 +102,6 @@ describe 'Visual tokens', :js do expect(page).to have_css('#js-dropdown-assignee', visible: false) end - it 'ends editing mode when scroll container is clicked' do - find('.scroll-container').click - - expect_filtered_search_input_empty - expect(page).to have_css('#js-dropdown-assignee', visible: false) - end - describe 'selecting static option from dropdown' do before do find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'None').click @@ -167,13 +153,6 @@ describe 'Visual tokens', :js do expect_filtered_search_input_empty expect(page).to have_css('#js-dropdown-milestone', visible: false) end - - it 'ends editing mode when scroll container is clicked' do - find('.scroll-container').click - - expect_filtered_search_input_empty - expect(page).to have_css('#js-dropdown-milestone', visible: false) - end end describe 'editing label token' do diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index c22ad0d20ef..8eb413bdd8d 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -278,12 +278,7 @@ describe 'GFM autocomplete', :js do end end - # This context has jsut one example in each contexts in order to improve spec performance. - context 'labels', :quarantine do - let!(:backend) { create(:label, project: project, title: 'backend') } - let!(:bug) { create(:label, project: project, title: 'bug') } - let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') } - + context 'labels' do it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do create(:label, project: project, title: label_xss_title) @@ -298,83 +293,6 @@ describe 'GFM autocomplete', :js do expect(find('.atwho-view-ul').text).to have_content('alert label') end end - - context 'when no labels are assigned' do - it 'shows labels' do - note = find('#note-body') - - # It should show all the labels on "~". - type(note, '~') - wait_for_requests - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show all the labels on "/label ~". - type(note, '/label ~') - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show all the labels on "/relabel ~". - type(note, '/relabel ~') - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show no labels on "/unlabel ~". - type(note, '/unlabel ~') - expect_labels(not_shown: [backend, bug, feature_proposal]) - end - end - - context 'when some labels are assigned' do - before do - issue.labels << [backend] - end - - it 'shows labels' do - note = find('#note-body') - - # It should show all the labels on "~". - type(note, '~') - wait_for_requests - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show only unset labels on "/label ~". - type(note, '/label ~') - expect_labels(shown: [bug, feature_proposal], not_shown: [backend]) - - # It should show all the labels on "/relabel ~". - type(note, '/relabel ~') - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show only set labels on "/unlabel ~". - type(note, '/unlabel ~') - expect_labels(shown: [backend], not_shown: [bug, feature_proposal]) - end - end - - context 'when all labels are assigned' do - before do - issue.labels << [backend, bug, feature_proposal] - end - - it 'shows labels' do - note = find('#note-body') - - # It should show all the labels on "~". - type(note, '~') - wait_for_requests - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show no labels on "/label ~". - type(note, '/label ~') - expect_labels(not_shown: [backend, bug, feature_proposal]) - - # It should show all the labels on "/relabel ~". - type(note, '/relabel ~') - expect_labels(shown: [backend, bug, feature_proposal]) - - # It should show all the labels on "/unlabel ~". - type(note, '/unlabel ~') - expect_labels(shown: [backend, bug, feature_proposal]) - end - end end shared_examples 'autocomplete suggestions' do diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb index 90d6841af0e..9909bfb5904 100644 --- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe 'User visits the profile preferences page' do + include Select2Helper + let(:user) { create(:user) } before do @@ -60,6 +62,28 @@ describe 'User visits the profile preferences page' do end end + describe 'User changes their language', :js do + it 'creates a flash message' do + select2('en', from: '#user_preferred_language') + click_button 'Save' + + wait_for_requests + + expect_preferences_saved_message + end + + it 'updates their preference' do + wait_for_requests + select2('eo', from: '#user_preferred_language') + click_button 'Save' + + wait_for_requests + refresh + + expect(page).to have_css('html[lang="eo"]') + end + end + def expect_preferences_saved_message page.within('.flash-container') do expect(page).to have_content('Preferences saved.') diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 6e6c299ee2e..828f6f9921e 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -9,6 +9,10 @@ describe 'Editing file blob', :js do let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] } let(:readme_file_path) { 'README.md' } + before do + stub_feature_flags(web_ide_default: false) + end + context 'as a developer' do let(:user) { create(:user) } let(:role) { :developer } @@ -77,7 +81,7 @@ describe 'Editing file blob', :js do click_link 'Preview' wait_for_requests - # the above generates two seperate lists (not embedded) in CommonMark + # the above generates two separate lists (not embedded) in CommonMark expect(page).to have_content("sublist") expect(page).not_to have_xpath("//ol//li//ul") end diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 97757e8da92..ee71c843b80 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -229,6 +229,38 @@ describe 'Branches' do end end + describe 'comparing branches' do + before do + sign_in(user) + project.add_developer(user) + end + + shared_examples 'compares branches' do + it 'compares branches' do + visit project_branches_path(project) + + page.within first('.all-branches li') do + click_link 'Compare' + end + + expect(page).to have_content 'Commits' + expect(page).to have_link 'Create merge request' + end + end + + context 'on a read-only instance' do + before do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + end + + it_behaves_like 'compares branches' + end + + context 'on a read-write instance' do + it_behaves_like 'compares branches' + end + end + def sorted_branches(repository, count:, sort_by:, state: nil) branches = repository.branches_sorted_by(sort_by) branches = branches.select { |b| state == 'active' ? b.active? : b.stale? } if state diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 0c517d5f490..66c6545204b 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -38,6 +38,23 @@ describe 'Environments page', :js do end end + describe 'with environments spanning multiple pages', :js do + before do + allow(Kaminari.config).to receive(:default_per_page).and_return(3) + create_list(:environment, 4, project: project, state: :available) + end + + it 'should render second page of pipelines' do + visit_environments(project, scope: 'available') + + find('.js-next-button').click + wait_for_requests + + expect(page).to have_selector('.gl-pagination .page', count: 2) + expect(find('.gl-pagination .page-item.active .page-link').text).to eq("2") + end + end + describe 'in stopped tab page' do it 'should show no environments' do visit_environments(project, scope: 'stopped') diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb index a4f94b7a76d..dd2964c2186 100644 --- a/spec/features/projects/files/user_creates_files_spec.rb +++ b/spec/features/projects/files/user_creates_files_spec.rb @@ -12,6 +12,8 @@ describe 'Projects > Files > User creates files' do let(:user) { create(:user) } before do + stub_feature_flags(web_ide_default: false) + project.add_maintainer(user) sign_in(user) end diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index 9eb65ec159c..ec3930c26db 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -9,6 +9,8 @@ describe 'Projects > Files > User edits files', :js do let(:user) { create(:user) } before do + stub_feature_flags(web_ide_default: false) + sign_in(user) end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 435fb229b69..f564ae34f11 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -13,16 +13,6 @@ describe 'Pages' do sign_in(user) end - shared_examples 'no pages deployed' do - it 'does not see anything to destroy' do - visit project_pages_path(project) - - expect(page).to have_content('Configure pages') - expect(page).not_to have_link('Remove pages') - expect(page).not_to have_text('Only the project owner can remove pages') - end - end - context 'when user is the owner' do before do project.namespace.update(owner: user) @@ -181,7 +171,12 @@ describe 'Pages' do end end - it_behaves_like 'no pages deployed' + it 'does not see anything to destroy' do + visit project_pages_path(project) + + expect(page).to have_content('Configure pages') + expect(page).not_to have_link('Remove pages') + end describe 'project settings page' do it 'renders "Pages" tab' do @@ -208,22 +203,6 @@ describe 'Pages' do end end - context 'when the user is not the owner' do - context 'when pages deployed' do - before do - allow_any_instance_of(Project).to receive(:pages_deployed?) { true } - end - - it 'sees "Only the project owner can remove pages" text' do - visit project_pages_path(project) - - expect(page).to have_text('Only the project owner can remove pages') - end - end - - it_behaves_like 'no pages deployed' - end - describe 'HTTPS settings', :js, :https_pages_enabled do before do project.namespace.update(owner: user) @@ -233,7 +212,7 @@ describe 'Pages' do it 'tries to change the setting' do visit project_pages_path(project) - expect(page).to have_content("Force domains with SSL certificates to use HTTPS") + expect(page).to have_content("Force HTTPS (requires valid certificates)") uncheck :project_pages_https_only @@ -282,58 +261,52 @@ describe 'Pages' do visit project_pages_path(project) expect(page).not_to have_field(:project_pages_https_only) - expect(page).not_to have_content('Force domains with SSL certificates to use HTTPS') + expect(page).not_to have_content('Force HTTPS (requires valid certificates)') expect(page).not_to have_button('Save') end end end describe 'Remove page' do - context 'when user is the owner' do - let(:project) { create :project, :repository } - - before do - project.namespace.update(owner: user) + let(:project) { create :project, :repository } + + context 'when pages are deployed' do + let(:pipeline) do + commit_sha = project.commit('HEAD').sha + + project.ci_pipelines.create( + ref: 'HEAD', + sha: commit_sha, + source: :push, + protected: false + ) end - context 'when pages are deployed' do - let(:pipeline) do - commit_sha = project.commit('HEAD').sha - - project.ci_pipelines.create( - ref: 'HEAD', - sha: commit_sha, - source: :push, - protected: false - ) - end - - let(:ci_build) do - create( - :ci_build, - project: project, - pipeline: pipeline, - ref: 'HEAD', - legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), - legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')) - ) - end + let(:ci_build) do + create( + :ci_build, + project: project, + pipeline: pipeline, + ref: 'HEAD', + legacy_artifacts_file: fixture_file_upload(File.join('spec/fixtures/pages.zip')), + legacy_artifacts_metadata: fixture_file_upload(File.join('spec/fixtures/pages.zip.meta')) + ) + end - before do - result = Projects::UpdatePagesService.new(project, ci_build).execute - expect(result[:status]).to eq(:success) - expect(project).to be_pages_deployed - end + before do + result = Projects::UpdatePagesService.new(project, ci_build).execute + expect(result[:status]).to eq(:success) + expect(project).to be_pages_deployed + end - it 'removes the pages' do - visit project_pages_path(project) + it 'removes the pages' do + visit project_pages_path(project) - expect(page).to have_link('Remove pages') + expect(page).to have_link('Remove pages') - click_link 'Remove pages' + click_link 'Remove pages' - expect(project.pages_deployed?).to be_falsey - end + expect(project.pages_deployed?).to be_falsey end end end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index ffa165c5440..88d7c9ef8bd 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -485,7 +485,7 @@ describe 'Pipelines', :js do it 'should show updated content' do visit project_pipelines_path(project) wait_for_requests - page.find('.js-next-button a').click + page.find('.js-next-button .page-link').click expect(page).to have_selector('.gl-pagination .page', count: 2) end diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb index 7cd5b12802b..74b9a2b20cd 100644 --- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb +++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb @@ -6,11 +6,17 @@ describe 'User activates issue tracker', :js do let(:url) { 'http://tracker.example.com' } - def fill_form(active = true) + def fill_short_form(active = true) check 'Active' if active fill_in 'service_project_url', with: url fill_in 'service_issues_url', with: "#{url}/:id" + end + + def fill_full_form(active = true) + fill_short_form(active) + check 'Active' if active + fill_in 'service_new_issue_url', with: url end @@ -21,14 +27,20 @@ describe 'User activates issue tracker', :js do visit project_settings_integrations_path(project) end - shared_examples 'external issue tracker activation' do |tracker:| + shared_examples 'external issue tracker activation' do |tracker:, skip_new_issue_url: false| describe 'user sets and activates the Service' do context 'when the connection test succeeds' do before do stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' }) click_link(tracker) - fill_form + + if skip_new_issue_url + fill_short_form + else + fill_full_form + end + click_button('Test settings and save changes') wait_for_requests end @@ -50,7 +62,13 @@ describe 'User activates issue tracker', :js do stub_request(:head, url).to_raise(HTTParty::Error) click_link(tracker) - fill_form + + if skip_new_issue_url + fill_short_form + else + fill_full_form + end + click_button('Test settings and save changes') wait_for_requests @@ -69,7 +87,13 @@ describe 'User activates issue tracker', :js do describe 'user sets the service but keeps it disabled' do before do click_link(tracker) - fill_form(false) + + if skip_new_issue_url + fill_short_form(false) + else + fill_full_form(false) + end + click_button('Save changes') end @@ -87,6 +111,7 @@ describe 'User activates issue tracker', :js do end it_behaves_like 'external issue tracker activation', tracker: 'Redmine' + it_behaves_like 'external issue tracker activation', tracker: 'YouTrack', skip_new_issue_url: true it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla' it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker' end diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb new file mode 100644 index 00000000000..bb6a030c1cf --- /dev/null +++ b/spec/features/projects/services/user_activates_youtrack_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe 'User activates issue tracker', :js do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:url) { 'http://tracker.example.com' } + + def fill_form(active = true) + check 'Active' if active + + fill_in 'service_project_url', with: url + fill_in 'service_issues_url', with: "#{url}/:id" + end + + before do + project.add_maintainer(user) + sign_in(user) + + visit project_settings_integrations_path(project) + end + + shared_examples 'external issue tracker activation' do |tracker:| + describe 'user sets and activates the Service' do + context 'when the connection test succeeds' do + before do + stub_request(:head, url).to_return(headers: { 'Content-Type' => 'application/json' }) + + click_link(tracker) + fill_form + click_button('Test settings and save changes') + wait_for_requests + end + + it 'activates the service' do + expect(page).to have_content("#{tracker} activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + + it 'shows the link in the menu' do + page.within('.nav-sidebar') do + expect(page).to have_link(tracker, href: url) + end + end + end + + context 'when the connection test fails' do + it 'activates the service' do + stub_request(:head, url).to_raise(HTTParty::Error) + + click_link(tracker) + fill_form + click_button('Test settings and save changes') + wait_for_requests + + expect(find('.flash-container-page')).to have_content 'Test failed.' + expect(find('.flash-container-page')).to have_content 'Save anyway' + + find('.flash-alert .flash-action').click + wait_for_requests + + expect(page).to have_content("#{tracker} activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + end + end + + describe 'user sets the service but keeps it disabled' do + before do + click_link(tracker) + fill_form(false) + click_button('Save changes') + end + + it 'saves but does not activate the service' do + expect(page).to have_content("#{tracker} settings saved, but not activated.") + expect(current_path).to eq(project_settings_integrations_path(project)) + end + + it 'does not show the external tracker link in the menu' do + page.within('.nav-sidebar') do + expect(page).not_to have_link(tracker, href: url) + end + end + end + end + + it_behaves_like 'external issue tracker activation', tracker: 'YouTrack' +end diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 49244c53a91..49058d1372a 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -170,7 +170,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do fill_in :wiki_content, with: "1. one\n - sublist\n" click_on "Preview" - # the above generates two seperate lists (not embedded) in CommonMark + # the above generates two separate lists (not embedded) in CommonMark expect(page).to have_content("sublist") expect(page).not_to have_xpath("//ol//li//ul") end diff --git a/spec/finders/concerns/finder_methods_spec.rb b/spec/finders/concerns/finder_methods_spec.rb index a4ad331f613..e074e53c2c5 100644 --- a/spec/finders/concerns/finder_methods_spec.rb +++ b/spec/finders/concerns/finder_methods_spec.rb @@ -12,7 +12,7 @@ describe FinderMethods do end def execute - Project.all + Project.all.order(id: :desc) end end end @@ -38,6 +38,16 @@ describe FinderMethods do it 'raises not found the user does not have access' do expect { finder.find_by!(id: unauthorized_project.id) }.to raise_error(ActiveRecord::RecordNotFound) end + + it 'ignores ordering' do + # Memoise the finder result so we can add message expectations to it + relation = finder.execute + allow(finder).to receive(:execute).and_return(relation) + + expect(relation).to receive(:reorder).with(nil).and_call_original + + finder.find_by!(id: authorized_project.id) + end end describe '#find' do @@ -66,5 +76,15 @@ describe FinderMethods do it 'returns nil when the user does not have access' do expect(finder.find_by(id: unauthorized_project.id)).to be_nil end + + it 'ignores ordering' do + # Memoise the finder result so we can add message expectations to it + relation = finder.execute + allow(finder).to receive(:execute).and_return(relation) + + expect(relation).to receive(:reorder).with(nil).and_call_original + + finder.find_by(id: authorized_project.id) + end end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 34cb09942be..47e2548c3d6 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -416,6 +416,36 @@ describe IssuesFinder do end end + context 'filtering by closed_at' do + let!(:closed_issue1) { create(:issue, project: project1, state: :closed, closed_at: 1.week.ago) } + let!(:closed_issue2) { create(:issue, project: project2, state: :closed, closed_at: 1.week.from_now) } + let!(:closed_issue3) { create(:issue, project: project2, state: :closed, closed_at: 2.weeks.from_now) } + + context 'through closed_after' do + let(:params) { { state: :closed, closed_after: closed_issue3.closed_at } } + + it 'returns issues closed on or after the given date' do + expect(issues).to contain_exactly(closed_issue3) + end + end + + context 'through closed_before' do + let(:params) { { state: :closed, closed_before: closed_issue1.closed_at } } + + it 'returns issues closed on or before the given date' do + expect(issues).to contain_exactly(closed_issue1) + end + end + + context 'through closed_after and closed_before' do + let(:params) { { state: :closed, closed_after: closed_issue2.closed_at, closed_before: closed_issue3.closed_at } } + + it 'returns issues closed between the given dates' do + expect(issues).to contain_exactly(closed_issue2, closed_issue3) + end + end + end + context 'filtering by reaction name' do context 'user searches by no reaction' do let(:params) { { my_reaction_emoji: 'None' } } @@ -460,6 +490,32 @@ describe IssuesFinder do end end + context 'filtering by confidential' do + set(:confidential_issue) { create(:issue, project: project1, confidential: true) } + + context 'no filtering' do + it 'returns all issues' do + expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, confidential_issue) + end + end + + context 'user filters confidential issues' do + let(:params) { { confidential: true } } + + it 'returns only confdential issues' do + expect(issues).to contain_exactly(confidential_issue) + end + end + + context 'user filters only public issues' do + let(:params) { { confidential: false } } + + it 'returns only confdential issues' do + expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) + end + end + end + context 'when the user is unauthorized' do let(:search_user) { nil } @@ -526,7 +582,7 @@ describe IssuesFinder do it 'returns the number of rows for the default state' do finder = described_class.new(user) - expect(finder.row_count).to eq(4) + expect(finder.row_count).to eq(5) end it 'returns the number of rows for a given state' do diff --git a/spec/fixtures/api/schemas/environment.json b/spec/fixtures/api/schemas/environment.json index f1d33e3ce7b..9a10ab18c30 100644 --- a/spec/fixtures/api/schemas/environment.json +++ b/spec/fixtures/api/schemas/environment.json @@ -20,6 +20,7 @@ "state": { "type": "string" }, "external_url": { "$ref": "types/nullable_string.json" }, "environment_type": { "$ref": "types/nullable_string.json" }, + "name_without_type": { "type": "string" }, "has_stop_action": { "type": "boolean" }, "environment_path": { "type": "string" }, "stop_path": { "type": "string" }, diff --git a/spec/frontend/__mocks__/file_mock.js b/spec/frontend/__mocks__/file_mock.js new file mode 100644 index 00000000000..08d725cd4e4 --- /dev/null +++ b/spec/frontend/__mocks__/file_mock.js @@ -0,0 +1 @@ +export default ''; diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js index a14031f43ed..b79e6e0fe7b 100644 --- a/spec/javascripts/gfm_auto_complete_spec.js +++ b/spec/frontend/gfm_auto_complete_spec.js @@ -6,55 +6,63 @@ import GfmAutoComplete from '~/gfm_auto_complete'; import 'vendor/jquery.caret'; import 'vendor/jquery.atwho'; -describe('GfmAutoComplete', function() { +import { TEST_HOST } from 'helpers/test_constants'; +import labelsFixture from 'fixtures/autocomplete_sources/labels.json'; // eslint-disable-line import/no-unresolved + +describe('GfmAutoComplete', () => { const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({ fetchData: () => {}, }); - describe('DefaultOptions.sorter', function() { - describe('assets loading', function() { - beforeEach(function() { - spyOn(GfmAutoComplete, 'isLoading').and.returnValue(true); + let atwhoInstance; + let sorterValue; + + describe('DefaultOptions.sorter', () => { + describe('assets loading', () => { + let items; - this.atwhoInstance = { setting: {} }; - this.items = []; + beforeEach(() => { + jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(true); - this.sorterValue = gfmAutoCompleteCallbacks.sorter.call(this.atwhoInstance, '', this.items); + atwhoInstance = { setting: {} }; + items = []; + + sorterValue = gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, '', items); }); - it('should disable highlightFirst', function() { - expect(this.atwhoInstance.setting.highlightFirst).toBe(false); + it('should disable highlightFirst', () => { + expect(atwhoInstance.setting.highlightFirst).toBe(false); }); - it('should return the passed unfiltered items', function() { - expect(this.sorterValue).toEqual(this.items); + it('should return the passed unfiltered items', () => { + expect(sorterValue).toEqual(items); }); }); - describe('assets finished loading', function() { - beforeEach(function() { - spyOn(GfmAutoComplete, 'isLoading').and.returnValue(false); - spyOn($.fn.atwho.default.callbacks, 'sorter'); + describe('assets finished loading', () => { + beforeEach(() => { + jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(false); + jest.spyOn($.fn.atwho.default.callbacks, 'sorter').mockImplementation(() => {}); }); - it('should enable highlightFirst if alwaysHighlightFirst is set', function() { - const atwhoInstance = { setting: { alwaysHighlightFirst: true } }; + it('should enable highlightFirst if alwaysHighlightFirst is set', () => { + atwhoInstance = { setting: { alwaysHighlightFirst: true } }; gfmAutoCompleteCallbacks.sorter.call(atwhoInstance); expect(atwhoInstance.setting.highlightFirst).toBe(true); }); - it('should enable highlightFirst if a query is present', function() { - const atwhoInstance = { setting: {} }; + it('should enable highlightFirst if a query is present', () => { + atwhoInstance = { setting: {} }; gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, 'query'); expect(atwhoInstance.setting.highlightFirst).toBe(true); }); - it('should call the default atwho sorter', function() { - const atwhoInstance = { setting: {} }; + it('should call the default atwho sorter', () => { + atwhoInstance = { setting: {} }; const query = 'query'; const items = []; @@ -71,7 +79,9 @@ describe('GfmAutoComplete', function() { const beforeInsert = (context, value) => gfmAutoCompleteCallbacks.beforeInsert.call(context, value); - const atwhoInstance = { setting: { skipSpecialCharacterTest: false } }; + beforeEach(() => { + atwhoInstance = { setting: { skipSpecialCharacterTest: false } }; + }); it('should not quote if value only contains alphanumeric charecters', () => { expect(beforeInsert(atwhoInstance, '@user1')).toBe('@user1'); @@ -96,7 +106,7 @@ describe('GfmAutoComplete', function() { }); }); - describe('DefaultOptions.matcher', function() { + describe('DefaultOptions.matcher', () => { const defaultMatcher = (context, flag, subtext) => gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext); @@ -108,7 +118,10 @@ describe('GfmAutoComplete', function() { hash[el] = null; return hash; }, {}); - const atwhoInstance = { setting: {}, app: { controllers: flagsHash } }; + + beforeEach(() => { + atwhoInstance = { setting: {}, app: { controllers: flagsHash } }; + }); const minLen = 1; const maxLen = 20; @@ -182,38 +195,38 @@ describe('GfmAutoComplete', function() { }); }); - describe('isLoading', function() { - it('should be true with loading data object item', function() { + describe('isLoading', () => { + it('should be true with loading data object item', () => { expect(GfmAutoComplete.isLoading({ name: 'loading' })).toBe(true); }); - it('should be true with loading data array', function() { + it('should be true with loading data array', () => { expect(GfmAutoComplete.isLoading(['loading'])).toBe(true); }); - it('should be true with loading data object array', function() { + it('should be true with loading data object array', () => { expect(GfmAutoComplete.isLoading([{ name: 'loading' }])).toBe(true); }); - it('should be false with actual array data', function() { + it('should be false with actual array data', () => { expect( GfmAutoComplete.isLoading([{ title: 'Foo' }, { title: 'Bar' }, { title: 'Qux' }]), ).toBe(false); }); - it('should be false with actual data item', function() { + it('should be false with actual data item', () => { expect(GfmAutoComplete.isLoading({ title: 'Foo' })).toBe(false); }); }); - describe('Issues.insertTemplateFunction', function() { - it('should return default template', function() { + describe('Issues.insertTemplateFunction', () => { + it('should return default template', () => { expect(GfmAutoComplete.Issues.insertTemplateFunction({ id: 5, title: 'Some Issue' })).toBe( '${atwho-at}${id}', // eslint-disable-line no-template-curly-in-string ); }); - it('should return reference when reference is set', function() { + it('should return reference when reference is set', () => { expect( GfmAutoComplete.Issues.insertTemplateFunction({ id: 5, @@ -224,14 +237,14 @@ describe('GfmAutoComplete', function() { }); }); - describe('Issues.templateFunction', function() { - it('should return html with id and title', function() { + describe('Issues.templateFunction', () => { + it('should return html with id and title', () => { expect(GfmAutoComplete.Issues.templateFunction({ id: 5, title: 'Some Issue' })).toBe( '<li><small>5</small> Some Issue</li>', ); }); - it('should replace id with reference if reference is set', function() { + it('should replace id with reference if reference is set', () => { expect( GfmAutoComplete.Issues.templateFunction({ id: 5, @@ -241,4 +254,90 @@ describe('GfmAutoComplete', function() { ).toBe('<li><small>grp/proj#5</small> Some Issue</li>'); }); }); + + describe('labels', () => { + const dataSources = { + labels: `${TEST_HOST}/autocomplete_sources/labels`, + }; + + const allLabels = labelsFixture; + const assignedLabels = allLabels.filter(label => label.set); + const unassignedLabels = allLabels.filter(label => !label.set); + + let autocomplete; + let $textarea; + + beforeEach(() => { + autocomplete = new GfmAutoComplete(dataSources); + $textarea = $('<textarea></textarea>'); + autocomplete.setup($textarea, { labels: true }); + }); + + afterEach(() => { + autocomplete.destroy(); + }); + + const triggerDropdown = text => { + $textarea + .trigger('focus') + .val(text) + .caret('pos', -1); + $textarea.trigger('keyup'); + + return new Promise(window.requestAnimationFrame); + }; + + const getDropdownItems = () => { + const dropdown = document.getElementById('at-view-labels'); + const items = dropdown.getElementsByTagName('li'); + return [].map.call(items, item => item.textContent.trim()); + }; + + const expectLabels = ({ input, output }) => + triggerDropdown(input).then(() => { + expect(getDropdownItems()).toEqual(output.map(label => label.title)); + }); + + describe('with no labels assigned', () => { + beforeEach(() => { + autocomplete.cachedData['~'] = [...unassignedLabels]; + }); + + it.each` + input | output + ${'~'} | ${unassignedLabels} + ${'/label ~'} | ${unassignedLabels} + ${'/relabel ~'} | ${unassignedLabels} + ${'/unlabel ~'} | ${[]} + `('$input shows $output.length labels', expectLabels); + }); + + describe('with some labels assigned', () => { + beforeEach(() => { + autocomplete.cachedData['~'] = allLabels; + }); + + it.each` + input | output + ${'~'} | ${allLabels} + ${'/label ~'} | ${unassignedLabels} + ${'/relabel ~'} | ${allLabels} + ${'/unlabel ~'} | ${assignedLabels} + `('$input shows $output.length labels', expectLabels); + }); + + describe('with all labels assigned', () => { + beforeEach(() => { + autocomplete.cachedData['~'] = [...assignedLabels]; + }); + + it.each` + input | output + ${'~'} | ${assignedLabels} + ${'/label ~'} | ${[]} + ${'/relabel ~'} | ${assignedLabels} + ${'/unlabel ~'} | ${assignedLabels} + `('$input shows $output.length labels', expectLabels); + }); + }); }); diff --git a/spec/javascripts/issuable_suggestions/components/app_spec.js b/spec/frontend/issuable_suggestions/components/app_spec.js index 7bb8e26b81a..7bb8e26b81a 100644 --- a/spec/javascripts/issuable_suggestions/components/app_spec.js +++ b/spec/frontend/issuable_suggestions/components/app_spec.js diff --git a/spec/javascripts/issuable_suggestions/components/item_spec.js b/spec/frontend/issuable_suggestions/components/item_spec.js index 7bd1fe678f4..7bd1fe678f4 100644 --- a/spec/javascripts/issuable_suggestions/components/item_spec.js +++ b/spec/frontend/issuable_suggestions/components/item_spec.js diff --git a/spec/javascripts/issuable_suggestions/mock_data.js b/spec/frontend/issuable_suggestions/mock_data.js index 4f0f9ef8d62..4f0f9ef8d62 100644 --- a/spec/javascripts/issuable_suggestions/mock_data.js +++ b/spec/frontend/issuable_suggestions/mock_data.js diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/frontend/lib/utils/ajax_cache_spec.js index dc0b04173bf..e2ee70b9d69 100644 --- a/spec/javascripts/lib/utils/ajax_cache_spec.js +++ b/spec/frontend/lib/utils/ajax_cache_spec.js @@ -94,68 +94,54 @@ describe('AjaxCache', () => { beforeEach(() => { mock = new MockAdapter(axios); - spyOn(axios, 'get').and.callThrough(); + jest.spyOn(axios, 'get'); }); afterEach(() => { mock.restore(); }); - it('stores and returns data from Ajax call if cache is empty', done => { + it('stores and returns data from Ajax call if cache is empty', () => { mock.onGet(dummyEndpoint).reply(200, dummyResponse); - AjaxCache.retrieve(dummyEndpoint) - .then(data => { - expect(data).toEqual(dummyResponse); - expect(AjaxCache.internalStorage[dummyEndpoint]).toEqual(dummyResponse); - }) - .then(done) - .catch(fail); + return AjaxCache.retrieve(dummyEndpoint).then(data => { + expect(data).toEqual(dummyResponse); + expect(AjaxCache.internalStorage[dummyEndpoint]).toEqual(dummyResponse); + }); }); - it('makes no Ajax call if request is pending', done => { + it('makes no Ajax call if request is pending', () => { mock.onGet(dummyEndpoint).reply(200, dummyResponse); - AjaxCache.retrieve(dummyEndpoint) - .then(done) - .catch(fail); - - AjaxCache.retrieve(dummyEndpoint) - .then(done) - .catch(fail); - - expect(axios.get.calls.count()).toBe(1); + return Promise.all([ + AjaxCache.retrieve(dummyEndpoint), + AjaxCache.retrieve(dummyEndpoint), + ]).then(() => { + expect(axios.get).toHaveBeenCalledTimes(1); + }); }); - it('returns undefined if Ajax call fails and cache is empty', done => { + it('returns undefined if Ajax call fails and cache is empty', () => { const errorMessage = 'Network Error'; mock.onGet(dummyEndpoint).networkError(); - AjaxCache.retrieve(dummyEndpoint) - .then(data => fail(`Received unexpected data: ${JSON.stringify(data)}`)) - .catch(error => { - expect(error.message).toBe(`${dummyEndpoint}: ${errorMessage}`); - expect(error.textStatus).toBe(errorMessage); - done(); - }) - .catch(fail); + expect.assertions(2); + return AjaxCache.retrieve(dummyEndpoint).catch(error => { + expect(error.message).toBe(`${dummyEndpoint}: ${errorMessage}`); + expect(error.textStatus).toBe(errorMessage); + }); }); - it('makes no Ajax call if matching data exists', done => { + it('makes no Ajax call if matching data exists', () => { AjaxCache.internalStorage[dummyEndpoint] = dummyResponse; - mock.onGet(dummyEndpoint).reply(() => { - fail(new Error('expected no Ajax call!')); - }); - AjaxCache.retrieve(dummyEndpoint) - .then(data => { - expect(data).toBe(dummyResponse); - }) - .then(done) - .catch(fail); + return AjaxCache.retrieve(dummyEndpoint).then(data => { + expect(data).toBe(dummyResponse); + expect(axios.get).not.toHaveBeenCalled(); + }); }); - it('makes Ajax call even if matching data exists when forceRequest parameter is provided', done => { + it('makes Ajax call even if matching data exists when forceRequest parameter is provided', () => { const oldDummyResponse = { important: 'old dummy data', }; @@ -164,21 +150,12 @@ describe('AjaxCache', () => { mock.onGet(dummyEndpoint).reply(200, dummyResponse); - // Call without forceRetrieve param - AjaxCache.retrieve(dummyEndpoint) - .then(data => { - expect(data).toBe(oldDummyResponse); - }) - .then(done) - .catch(fail); - - // Call with forceRetrieve param - AjaxCache.retrieve(dummyEndpoint, true) - .then(data => { - expect(data).toEqual(dummyResponse); - }) - .then(done) - .catch(fail); + return Promise.all([ + AjaxCache.retrieve(dummyEndpoint), + AjaxCache.retrieve(dummyEndpoint, true), + ]).then(data => { + expect(data).toEqual([oldDummyResponse, dummyResponse]); + }); }); }); }); diff --git a/spec/frontend/notes/components/__snapshots__/discussion_jump_to_next_button_spec.js.snap b/spec/frontend/notes/components/__snapshots__/discussion_jump_to_next_button_spec.js.snap new file mode 100644 index 00000000000..11d65ced180 --- /dev/null +++ b/spec/frontend/notes/components/__snapshots__/discussion_jump_to_next_button_spec.js.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`JumpToNextDiscussionButton matches the snapshot 1`] = ` +<div + class="btn-group" + role="group" +> + <button + class="btn btn-default discussion-next-btn" + data-original-title="Jump to next unresolved discussion" + title="" + > + <icon-stub + cssclasses="" + name="comment-next" + size="16" + /> + </button> +</div> +`; diff --git a/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js new file mode 100644 index 00000000000..989b0458481 --- /dev/null +++ b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js @@ -0,0 +1,30 @@ +import JumpToNextDiscussionButton from '~/notes/components/discussion_jump_to_next_button.vue'; +import { shallowMount } from '@vue/test-utils'; + +describe('JumpToNextDiscussionButton', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallowMount(JumpToNextDiscussionButton, { + sync: false, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('matches the snapshot', () => { + expect(wrapper.vm.$el).toMatchSnapshot(); + }); + + it('emits onClick event on button click', () => { + const button = wrapper.find({ ref: 'button' }); + + button.trigger('click'); + + expect(wrapper.emitted()).toEqual({ + onClick: [[]], + }); + }); +}); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 7ad2e97e7e6..d892889b98d 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -1,3 +1,7 @@ +import Vue from 'vue'; +import Translate from '~/vue_shared/translate'; +import axios from '~/lib/utils/axios_utils'; + const testTimeoutInMs = 300; jest.setTimeout(testTimeoutInMs); @@ -14,3 +18,17 @@ afterEach(() => { throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`); } }); + +// fail tests for unmocked requests +beforeEach(done => { + axios.defaults.adapter = config => { + const error = new Error(`Unexpected unmocked request: ${JSON.stringify(config, null, 2)}`); + error.config = config; + done.fail(error); + return Promise.reject(error); + }; + + done(); +}); + +Vue.use(Translate); diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb new file mode 100644 index 00000000000..a229d29afa6 --- /dev/null +++ b/spec/graphql/features/authorization_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Gitlab::Graphql::Authorization' do + set(:user) { create(:user) } + + let(:test_object) { double(name: 'My name') } + let(:object_type) { object_type_class } + let(:query_type) { query_type_class(object_type, test_object) } + let(:schema) { schema_class(query_type) } + + let(:execute) do + schema.execute( + query_string, + context: { current_user: user }, + variables: {} + ) + end + + let(:result) { execute['data'] } + + before do + # By default, disallow all permissions. + allow(Ability).to receive(:allowed?).and_return(false) + end + + describe 'authorizing with a single permission' do + let(:query_string) { '{ singlePermission() { name } }' } + + subject { result['singlePermission'] } + + it 'should return the protected field when user has permission' do + permit(:foo) + + expect(subject['name']).to eq(test_object.name) + end + + it 'should return nil when user is not authorized' do + expect(subject).to be_nil + end + end + + describe 'authorizing with an Array of permissions' do + let(:query_string) { '{ permissionCollection() { name } }' } + + subject { result['permissionCollection'] } + + it 'should return the protected field when user has all permissions' do + permit(:foo, :bar) + + expect(subject['name']).to eq(test_object.name) + end + + it 'should return nil when user only has one of the permissions' do + permit(:foo) + + expect(subject).to be_nil + end + + it 'should return nil when user only has none of the permissions' do + expect(subject).to be_nil + end + end + + private + + def permit(*permissions) + permissions.each do |permission| + allow(Ability).to receive(:allowed?).with(user, permission, test_object).and_return(true) + end + end + + def object_type_class + Class.new(Types::BaseObject) do + graphql_name 'TestObject' + + field :name, GraphQL::STRING_TYPE, null: true + end + end + + def query_type_class(type, object) + Class.new(Types::BaseObject) do + graphql_name 'TestQuery' + + field :single_permission, type, + null: true, + authorize: :foo, + resolve: ->(obj, args, ctx) { object } + + field :permission_collection, type, + null: true, + resolve: ->(obj, args, ctx) { object } do + authorize [:foo, :bar] + end + end + end + + def schema_class(query) + Class.new(GraphQL::Schema) do + use Gitlab::Graphql::Authorize + + query(query) + end + end +end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 66de372e9fe..5f9c180cbb7 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -5,16 +5,63 @@ describe Resolvers::IssuesResolver do let(:current_user) { create(:user) } set(:project) { create(:project) } - set(:issue) { create(:issue, project: project) } - set(:issue2) { create(:issue, project: project, title: 'foo') } + set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) } + set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) } + set(:label1) { create(:label, project: project) } + set(:label2) { create(:label, project: project) } before do project.add_developer(current_user) + create(:label_link, label: label1, target: issue1) + create(:label_link, label: label1, target: issue2) + create(:label_link, label: label2, target: issue2) end describe '#resolve' do it 'finds all issues' do - expect(resolve_issues).to contain_exactly(issue, issue2) + expect(resolve_issues).to contain_exactly(issue1, issue2) + end + + it 'filters by state' do + expect(resolve_issues(state: 'opened')).to contain_exactly(issue1) + expect(resolve_issues(state: 'closed')).to contain_exactly(issue2) + end + + it 'filters by labels' do + expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2) + expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2) + end + + describe 'filters by created_at' do + it 'filters by created_before' do + expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1) + end + + it 'filters by created_after' do + expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2) + end + end + + describe 'filters by updated_at' do + it 'filters by updated_before' do + expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1) + end + + it 'filters by updated_after' do + expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2) + end + end + + describe 'filters by closed_at' do + let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) } + + it 'filters by closed_before' do + expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3) + end + + it 'filters by closed_after' do + expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2) + end end it 'searches issues' do @@ -22,7 +69,7 @@ describe Resolvers::IssuesResolver do end it 'sort issues' do - expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue] + expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1] end it 'returns issues user can see' do @@ -30,31 +77,31 @@ describe Resolvers::IssuesResolver do create(:issue, confidential: true) - expect(resolve_issues).to contain_exactly(issue, issue2) + expect(resolve_issues).to contain_exactly(issue1, issue2) end it 'finds a specific issue with iid' do - expect(resolve_issues(iid: issue.iid)).to contain_exactly(issue) + expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1) end it 'finds a specific issue with iids' do - expect(resolve_issues(iids: issue.iid)).to contain_exactly(issue) + expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1) end it 'finds multiple issues with iids' do - expect(resolve_issues(iids: [issue.iid, issue2.iid])) - .to contain_exactly(issue, issue2) + expect(resolve_issues(iids: [issue1.iid, issue2.iid])) + .to contain_exactly(issue1, issue2) end it 'finds only the issues within the project we are looking at' do another_project = create(:project) - iids = [issue, issue2].map(&:iid) + iids = [issue1, issue2].map(&:iid) iids.each do |iid| create(:issue, project: another_project, iid: iid) end - expect(resolve_issues(iids: iids)).to contain_exactly(issue, issue2) + expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2) end end diff --git a/spec/graphql/types/issuable_state_enum_spec.rb b/spec/graphql/types/issuable_state_enum_spec.rb new file mode 100644 index 00000000000..65a80fa4176 --- /dev/null +++ b/spec/graphql/types/issuable_state_enum_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['IssuableState'] do + it { expect(described_class.graphql_name).to eq('IssuableState') } + + it_behaves_like 'issuable state' +end diff --git a/spec/graphql/types/issue_state_enum_spec.rb b/spec/graphql/types/issue_state_enum_spec.rb new file mode 100644 index 00000000000..de19e6fc505 --- /dev/null +++ b/spec/graphql/types/issue_state_enum_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['IssueState'] do + it { expect(described_class.graphql_name).to eq('IssueState') } + + it_behaves_like 'issuable state' +end diff --git a/spec/graphql/types/merge_request_state_enum_spec.rb b/spec/graphql/types/merge_request_state_enum_spec.rb new file mode 100644 index 00000000000..626e33b18d3 --- /dev/null +++ b/spec/graphql/types/merge_request_state_enum_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GitlabSchema.types['MergeRequestState'] do + it { expect(described_class.graphql_name).to eq('MergeRequestState') } + + it_behaves_like 'issuable state' + + it 'exposes all the existing merge request states' do + expect(described_class.values.keys).to include('merged') + end +end diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb new file mode 100644 index 00000000000..8d717b968dd --- /dev/null +++ b/spec/helpers/appearances_helper_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AppearancesHelper do + before do + user = create(:user) + allow(helper).to receive(:current_user).and_return(user) + end + + describe '#header_message' do + it 'returns nil when header message field is not set' do + create(:appearance) + + expect(helper.header_message).to be_nil + end + + context 'when header message is set' do + it 'includes current message' do + message = "Foo bar" + create(:appearance, header_message: message) + + expect(helper.header_message).to include(message) + end + end + end + + describe '#footer_message' do + it 'returns nil when footer message field is not set' do + create(:appearance) + + expect(helper.footer_message).to be_nil + end + + context 'when footer message is set' do + it 'includes current message' do + message = "Foo bar" + create(:appearance, footer_message: message) + + expect(helper.footer_message).to include(message) + end + end + end + + describe '#brand_image' do + let!(:appearance) { create(:appearance, :with_logo) } + + context 'when there is a logo' do + it 'returns a path' do + expect(helper.brand_image).to match(%r(img data-src="/uploads/-/system/appearance/.*png)) + end + end + + context 'when there is a logo but no associated upload' do + before do + # Legacy attachments were not tracked in the uploads table + appearance.logo.upload.destroy + appearance.reload + end + + it 'falls back to using the original path' do + expect(helper.brand_image).to match(%r(img data-src="/uploads/-/system/appearance/.*png)) + end + end + end + + describe '#brand_title' do + it 'returns the default CE title when no appearance is present' do + allow(helper) + .to receive(:current_appearance) + .and_return(nil) + + expect(helper.brand_title).to eq('GitLab Community Edition') + end + end +end diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index f709f152c92..2bc3933809f 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -50,12 +50,20 @@ describe BlobHelper do end it 'returns a link with the proper route' do + stub_feature_flags(web_ide_default: false) link = edit_blob_button(project, 'master', 'README.md') expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md") end + it 'returns a link with a Web IDE route' do + link = edit_blob_button(project, 'master', 'README.md') + + expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md") + end + it 'returns a link with the passed link_opts on the expected route' do + stub_feature_flags(web_ide_default: false) link = edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 }) expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md?mr_id=10") diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index c04f679bcf0..012678db9c2 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -236,4 +236,17 @@ describe LabelsHelper do expect(labels_filter_path(format: :json)).to eq(dashboard_labels_path(format: :json)) end end + + describe 'labels_sorted_by_title' do + it 'sorts labels alphabetically' do + label1 = double(:label, title: 'a') + label2 = double(:label, title: 'B') + label3 = double(:label, title: 'c') + label4 = double(:label, title: 'D') + labels = [label1, label2, label3, label4] + + expect(labels_sorted_by_title(labels)) + .to match_array([label2, label4, label1, label3]) + end + end end diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index 4c395248644..e0e8ebd0c3c 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -110,6 +110,13 @@ describe PreferencesHelper do end end + describe '#language_choices' do + it 'returns an array of all available languages' do + expect(helper.language_choices).to be_an(Array) + expect(helper.language_choices.map(&:second)).to eq(Gitlab::I18n.available_locales) + end + end + def stub_user(messages = {}) if messages.empty? allow(helper).to receive(:current_user).and_return(nil) diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index 04c8ab44405..fec01b1f0a3 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -87,7 +87,7 @@ describe('Pipelines table in Commits and Merge requests', function() { }; vm.$nextTick(() => { - vm.$el.querySelector('.js-next-button a').click(); + vm.$el.querySelector('.js-next-button .page-link').click(); expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' }); done(); diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index 5abdfe695d0..bce6113f75a 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -4,12 +4,13 @@ import { TEST_HOST } from 'spec/test_constants'; import App from '~/diffs/components/app.vue'; import NoChanges from '~/diffs/components/no_changes.vue'; import DiffFile from '~/diffs/components/diff_file.vue'; +import Mousetrap from 'mousetrap'; import createDiffsStore from '../create_diffs_store'; describe('diffs/components/app', () => { const oldMrTabs = window.mrTabs; let store; - let vm; + let wrapper; function createComponent(props = {}, extendStore = () => {}) { const localVue = createLocalVue(); @@ -21,7 +22,7 @@ describe('diffs/components/app', () => { extendStore(store); - vm = shallowMount(localVue.extend(App), { + wrapper = shallowMount(localVue.extend(App), { localVue, propsData: { endpoint: `${TEST_HOST}/diff/endpoint`, @@ -38,7 +39,6 @@ describe('diffs/components/app', () => { // setup globals (needed for component to mount :/) window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']); window.mrTabs.expandViewContainer = jasmine.createSpy(); - window.location.hash = 'ABC_123'; }); afterEach(() => { @@ -46,25 +46,44 @@ describe('diffs/components/app', () => { window.mrTabs = oldMrTabs; // reset component - vm.destroy(); + wrapper.destroy(); }); it('does not show commit info', () => { createComponent(); - expect(vm.contains('.blob-commit-info')).toBe(false); + expect(wrapper.contains('.blob-commit-info')).toBe(false); }); - it('sets highlighted row if hash exists in location object', done => { - createComponent({ - shouldShow: true, + describe('row highlighting', () => { + beforeEach(() => { + window.location.hash = 'ABC_123'; }); - // Component uses $nextTick so we wait until that has finished - setTimeout(() => { - expect(store.state.diffs.highlightedRow).toBe('ABC_123'); + it('sets highlighted row if hash exists in location object', done => { + createComponent({ + shouldShow: true, + }); - done(); + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.highlightedRow).toBe('ABC_123'); + + done(); + }); + }); + + it('marks current diff file based on currently highlighted row', done => { + createComponent({ + shouldShow: true, + }); + + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.currentDiffFileId).toBe('ABC'); + + done(); + }); }); }); @@ -76,7 +95,7 @@ describe('diffs/components/app', () => { it('sets initial width when no localStorage has been set', () => { createComponent(); - expect(vm.vm.treeWidth).toEqual(320); + expect(wrapper.vm.treeWidth).toEqual(320); }); it('sets initial width to localStorage size', () => { @@ -84,13 +103,26 @@ describe('diffs/components/app', () => { createComponent(); - expect(vm.vm.treeWidth).toEqual(200); + expect(wrapper.vm.treeWidth).toEqual(200); }); it('sets width of tree list', () => { createComponent(); - expect(vm.find('.js-diff-tree-list').element.style.width).toEqual('320px'); + expect(wrapper.find('.js-diff-tree-list').element.style.width).toEqual('320px'); + }); + }); + + it('marks current diff file based on currently highlighted row', done => { + createComponent({ + shouldShow: true, + }); + + // Component uses $nextTick so we wait until that has finished + setTimeout(() => { + expect(store.state.diffs.currentDiffFileId).toBe('ABC'); + + done(); }); }); @@ -98,7 +130,7 @@ describe('diffs/components/app', () => { it('renders empty state when no diff files exist', () => { createComponent(); - expect(vm.contains(NoChanges)).toBe(true); + expect(wrapper.contains(NoChanges)).toBe(true); }); it('does not render empty state when diff files exist', () => { @@ -108,8 +140,8 @@ describe('diffs/components/app', () => { }); }); - expect(vm.contains(NoChanges)).toBe(false); - expect(vm.findAll(DiffFile).length).toBe(1); + expect(wrapper.contains(NoChanges)).toBe(false); + expect(wrapper.findAll(DiffFile).length).toBe(1); }); it('does not render empty state when versions match', () => { @@ -118,7 +150,161 @@ describe('diffs/components/app', () => { store.state.diffs.mergeRequestDiff = { version_index: 1 }; }); - expect(vm.contains(NoChanges)).toBe(false); + expect(wrapper.contains(NoChanges)).toBe(false); + }); + }); + + describe('keyboard shortcut navigation', () => { + const mappings = { + '[': -1, + k: -1, + ']': +1, + j: +1, + }; + let spy; + + describe('visible app', () => { + beforeEach(() => { + spy = jasmine.createSpy('spy'); + + createComponent({ + shouldShow: true, + }); + wrapper.setMethods({ + jumpToFile: spy, + }); + }); + + it('calls `jumpToFile()` with correct parameter whenever pre-defined key is pressed', done => { + wrapper.vm + .$nextTick() + .then(() => { + Object.keys(mappings).forEach(function(key) { + Mousetrap.trigger(key); + + expect(spy.calls.mostRecent().args).toEqual([mappings[key]]); + }); + + expect(spy.calls.count()).toEqual(Object.keys(mappings).length); + }) + .then(done) + .catch(done.fail); + }); + + it('does not call `jumpToFile()` when unknown key is pressed', done => { + wrapper.vm + .$nextTick() + .then(() => { + Mousetrap.trigger('d'); + + expect(spy).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('hideen app', () => { + beforeEach(() => { + spy = jasmine.createSpy('spy'); + + createComponent({ + shouldShow: false, + }); + wrapper.setMethods({ + jumpToFile: spy, + }); + }); + + it('stops calling `jumpToFile()` when application is hidden', done => { + wrapper.vm + .$nextTick() + .then(() => { + Object.keys(mappings).forEach(function(key) { + Mousetrap.trigger(key); + + expect(spy).not.toHaveBeenCalled(); + }); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + + describe('jumpToFile', () => { + let spy; + + beforeEach(() => { + spy = jasmine.createSpy(); + + createComponent({}, () => { + store.state.diffs.diffFiles = [ + { file_hash: '111', file_path: '111.js' }, + { file_hash: '222', file_path: '222.js' }, + { file_hash: '333', file_path: '333.js' }, + ]; + }); + + wrapper.setMethods({ + scrollToFile: spy, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('jumps to next and previous files in the list', done => { + wrapper.vm + .$nextTick() + .then(() => { + wrapper.vm.jumpToFile(+1); + + expect(spy.calls.mostRecent().args).toEqual(['222.js']); + store.state.diffs.currentDiffFileId = '222'; + wrapper.vm.jumpToFile(+1); + + expect(spy.calls.mostRecent().args).toEqual(['333.js']); + store.state.diffs.currentDiffFileId = '333'; + wrapper.vm.jumpToFile(-1); + + expect(spy.calls.mostRecent().args).toEqual(['222.js']); + }) + .then(done) + .catch(done.fail); + }); + + it('does not jump to previous file from the first one', done => { + wrapper.vm + .$nextTick() + .then(() => { + store.state.diffs.currentDiffFileId = '333'; + + expect(wrapper.vm.currentDiffIndex).toEqual(2); + + wrapper.vm.jumpToFile(+1); + + expect(wrapper.vm.currentDiffIndex).toEqual(2); + expect(spy).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('does not jump to next file from the last one', done => { + wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.vm.currentDiffIndex).toEqual(0); + + wrapper.vm.jumpToFile(-1); + + expect(wrapper.vm.currentDiffIndex).toEqual(0); + expect(spy).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index acff80bca62..e47c7906fcb 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -100,9 +100,10 @@ describe('DiffsStoreActions', () => { }); describe('setHighlightedRow', () => { - it('should set lineHash and fileHash of highlightedRow', () => { + it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => { testAction(setHighlightedRow, 'ABC_123', {}, [ { type: types.SET_HIGHLIGHTED_ROW, payload: 'ABC_123' }, + { type: types.UPDATE_CURRENT_DIFF_FILE_ID, payload: 'ABC' }, ]); }); }); @@ -713,22 +714,6 @@ describe('DiffsStoreActions', () => { expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, 'test'); }); - - it('resets currentDiffId after timeout', () => { - const state = { - treeEntries: { - path: { - fileHash: 'test', - }, - }, - }; - - scrollToFile({ state, commit }, 'path'); - - jasmine.clock().tick(1000); - - expect(commit.calls.argsFor(1)).toEqual([types.UPDATE_CURRENT_DIFF_FILE_ID, '']); - }); }); describe('toggleShowTreeList', () => { diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js index 0ab88e6b2aa..eab5703dfb2 100644 --- a/spec/javascripts/diffs/store/getters_spec.js +++ b/spec/javascripts/diffs/store/getters_spec.js @@ -270,4 +270,24 @@ describe('Diffs Module Getters', () => { expect(getters.diffFilesLength(localState)).toBe(2); }); }); + + describe('currentDiffIndex', () => { + it('returns index of currently selected diff in diffList', () => { + localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }]; + localState.currentDiffFileId = '222'; + + expect(getters.currentDiffIndex(localState)).toEqual(1); + + localState.currentDiffFileId = '333'; + + expect(getters.currentDiffIndex(localState)).toEqual(2); + }); + + it('returns 0 if no diff is selected yet or diff is not found', () => { + localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }]; + localState.currentDiffFileId = ''; + + expect(getters.currentDiffIndex(localState)).toEqual(0); + }); + }); }); diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js index 52895f35f3a..ecd28594873 100644 --- a/spec/javascripts/environments/environment_table_spec.js +++ b/spec/javascripts/environments/environment_table_spec.js @@ -31,4 +31,224 @@ describe('Environment table', () => { expect(vm.$el.getAttribute('class')).toContain('ci-table'); }); + + describe('sortEnvironments', () => { + it('should sort environments by last updated', () => { + const mockItems = [ + { + name: 'old', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2019, 0, 5).toISOString(), + }, + }, + { + name: 'new', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2019, 1, 5).toISOString(), + }, + }, + { + name: 'older', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2018, 0, 5).toISOString(), + }, + }, + { + name: 'an environment with no deployment', + }, + ]; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + const [old, newer, older, noDeploy] = mockItems; + + expect(vm.sortEnvironments(mockItems)).toEqual([newer, old, older, noDeploy]); + }); + + it('should push environments with no deployments to the bottom', () => { + const mockItems = [ + { + name: 'production', + size: 1, + id: 2, + state: 'available', + external_url: 'https://google.com/production', + environment_type: null, + last_deployment: null, + has_stop_action: false, + environment_path: '/Commit451/lab-coat/environments/2', + stop_path: '/Commit451/lab-coat/environments/2/stop', + folder_path: '/Commit451/lab-coat/environments/folders/production', + created_at: '2019-01-17T16:26:10.064Z', + updated_at: '2019-01-17T16:27:37.717Z', + can_stop: true, + }, + { + name: 'review/225addcibuildstatus', + size: 2, + isFolder: true, + isLoadingFolderContent: false, + folderName: 'review', + isOpen: false, + children: [], + id: 12, + state: 'available', + external_url: 'https://google.com/review/225addcibuildstatus', + environment_type: 'review', + last_deployment: null, + has_stop_action: false, + environment_path: '/Commit451/lab-coat/environments/12', + stop_path: '/Commit451/lab-coat/environments/12/stop', + folder_path: '/Commit451/lab-coat/environments/folders/review', + created_at: '2019-01-17T16:27:37.877Z', + updated_at: '2019-01-17T16:27:37.883Z', + can_stop: true, + }, + { + name: 'staging', + size: 1, + id: 1, + state: 'available', + external_url: 'https://google.com/staging', + environment_type: null, + last_deployment: { + created_at: '2019-01-17T16:26:15.125Z', + scheduled_actions: [], + }, + }, + ]; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + const [prod, review, staging] = mockItems; + + expect(vm.sortEnvironments(mockItems)).toEqual([review, staging, prod]); + }); + + it('should sort environments by folder first', () => { + const mockItems = [ + { + name: 'old', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2019, 0, 5).toISOString(), + }, + }, + { + name: 'new', + size: 3, + isFolder: false, + last_deployment: { + created_at: new Date(2019, 1, 5).toISOString(), + }, + }, + { + name: 'older', + size: 3, + isFolder: true, + children: [], + }, + ]; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + const [old, newer, older] = mockItems; + + expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]); + }); + + it('should break ties by name', () => { + const mockItems = [ + { + name: 'old', + isFolder: false, + }, + { + name: 'new', + isFolder: false, + }, + { + folderName: 'older', + isFolder: true, + }, + ]; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + const [old, newer, older] = mockItems; + + expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]); + }); + }); + + describe('sortedEnvironments', () => { + it('it should sort children as well', () => { + const mockItems = [ + { + name: 'production', + last_deployment: null, + }, + { + name: 'review/225addcibuildstatus', + isFolder: true, + folderName: 'review', + isOpen: true, + children: [ + { + name: 'review/225addcibuildstatus', + last_deployment: { + created_at: '2019-01-17T16:26:15.125Z', + }, + }, + { + name: 'review/master', + last_deployment: { + created_at: '2019-02-17T16:26:15.125Z', + }, + }, + ], + }, + { + name: 'staging', + last_deployment: { + created_at: '2019-01-17T16:26:15.125Z', + }, + }, + ]; + const [production, review, staging] = mockItems; + const [addcibuildstatus, master] = mockItems[1].children; + + vm = mountComponent(Component, { + environments: mockItems, + canReadEnvironment: true, + }); + + expect(vm.sortedEnvironments.map(env => env.name)).toEqual([ + review.name, + staging.name, + production.name, + ]); + + expect(vm.sortedEnvironments[0].children).toEqual([master, addcibuildstatus]); + }); + }); }); diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js index 9220f7a264f..b6a244f7cd3 100644 --- a/spec/javascripts/environments/environments_app_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -94,7 +94,7 @@ describe('Environment', () => { it('should make an API request when page is clicked', done => { spyOn(component, 'updateContent'); setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(5) a').click(); + component.$el.querySelector('.gl-pagination li:nth-child(5) .page-link').click(); expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); done(); diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index d9ee7e74e28..69ddd26eef1 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -107,7 +107,7 @@ describe('Environments Folder View', () => { it('should make an API request when changing page', done => { spyOn(component, 'updateContent'); setTimeout(() => { - component.$el.querySelector('.gl-pagination .js-last-button a').click(); + component.$el.querySelector('.gl-pagination .js-last-button .page-link').click(); expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb new file mode 100644 index 00000000000..c117fb7cd24 --- /dev/null +++ b/spec/javascripts/fixtures/autocomplete_sources.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do + include JavaScriptFixturesHelpers + + set(:admin) { create(:admin) } + set(:group) { create(:group, name: 'frontend-fixtures') } + set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') } + set(:issue) { create(:issue, project: project) } + + before(:all) do + clean_frontend_fixtures('autocomplete_sources/') + end + + before do + sign_in(admin) + end + + it 'autocomplete_sources/labels.json' do |example| + issue.labels << create(:label, project: project, title: 'bug') + issue.labels << create(:label, project: project, title: 'critical') + + create(:label, project: project, title: 'feature') + create(:label, project: project, title: 'documentation') + + get :labels, + format: :json, + params: { + namespace_id: group.path, + project_id: project.path, + type: issue.class.name, + type_id: issue.id + } + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end +end diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb index 1b2a3b484bb..cd66d98f92a 100644 --- a/spec/javascripts/fixtures/blob.rb +++ b/spec/javascripts/fixtures/blob.rb @@ -15,6 +15,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do before do sign_in(admin) + allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') end after do diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb index f0e4bb50c67..295f13b34a4 100644 --- a/spec/javascripts/fixtures/commit.rb +++ b/spec/javascripts/fixtures/commit.rb @@ -16,6 +16,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller before do project.add_maintainer(user) sign_in(user) + allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') end it 'commit/show.html.raw' do |example| diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb index efbda955972..a333d9c0150 100644 --- a/spec/javascripts/fixtures/deploy_keys.rb +++ b/spec/javascripts/fixtures/deploy_keys.rb @@ -25,7 +25,7 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control render_views it 'deploy_keys/keys.json' do |example| - create(:deploy_key, public: true) + create(:rsa_deploy_key_2048, public: true) project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com') internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com') create(:deploy_keys_project, project: project, deploy_key: project_key) diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb index f8d55fc97c3..03136f4e661 100644 --- a/spec/javascripts/fixtures/groups.rb +++ b/spec/javascripts/fixtures/groups.rb @@ -4,7 +4,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do include JavaScriptFixturesHelpers let(:admin) { create(:admin) } - let(:group) { create(:group, name: 'frontend-fixtures-group' )} + let(:group) { create(:group, name: 'frontend-fixtures-group', runners_token: 'runnerstoken:intabulasreferre')} render_views diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 18fb1bebf8b..9b8e90c2a43 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller do include JavaScriptFixturesHelpers - let(:admin) { create(:admin) } + let(:admin) { create(:admin, feed_token: 'feedtoken:coldfeed') } let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') } diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index 26e81f06c0b..eb37be87e1d 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -35,6 +35,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont before do sign_in(admin) + allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo']) end after do @@ -54,8 +55,10 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont end it 'merge_requests/merged_merge_request.html.raw' do |example| - allow_any_instance_of(MergeRequest).to receive(:source_branch_exists?).and_return(true) - allow_any_instance_of(MergeRequest).to receive(:can_remove_source_branch?).and_return(true) + expect_next_instance_of(MergeRequest) do |merge_request| + allow(merge_request).to receive(:source_branch_exists?).and_return(true) + allow(merge_request).to receive(:can_remove_source_branch?).and_return(true) + end render_merge_request(example.description, merged_merge_request) end diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index 9b48646f8f0..85f02923804 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -3,13 +3,13 @@ require 'spec_helper' describe 'Projects (JavaScript fixtures)', type: :controller do include JavaScriptFixturesHelpers + runners_token = 'runnerstoken:intabulasreferre' + let(:admin) { create(:admin) } let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project, namespace: namespace, path: 'builds-project') } + let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token) } let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff') } - let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2') } - let!(:variable1) { create(:ci_variable, project: project_variable_populated) } - let!(:variable2) { create(:ci_variable, project: project_variable_populated) } + let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) } render_views @@ -20,6 +20,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do before do project.add_maintainer(admin) sign_in(admin) + allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') end after do @@ -70,6 +71,9 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end it 'projects/ci_cd_settings_with_variables.html.raw' do |example| + create(:ci_variable, project: project_variable_populated) + create(:ci_variable, project: project_variable_populated) + get :show, params: { namespace_id: project_variable_populated.namespace.to_param, project_id: project_variable_populated diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb index a14837e4d4a..bcd6546f3df 100644 --- a/spec/javascripts/fixtures/snippet.rb +++ b/spec/javascripts/fixtures/snippet.rb @@ -7,7 +7,6 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') } let(:snippet) { create(:personal_snippet, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) } - let!(:snippet_note) { create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') } render_views @@ -17,6 +16,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do before do sign_in(admin) + allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo']) end after do @@ -24,6 +24,8 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do end it 'snippets/show.html.raw' do |example| + create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') + get(:show, params: { id: snippet.to_param }) expect(response).to be_success diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index f0aa874bf75..5cdbadef639 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -3,7 +3,7 @@ require 'spec_helper' context 'U2F' do include JavaScriptFixturesHelpers - let(:user) { create(:user, :two_factor_via_u2f) } + let(:user) { create(:user, :two_factor_via_u2f, otp_secret: 'otpsecret:coolkids') } before(:all) do clean_frontend_fixtures('u2f/') @@ -33,6 +33,7 @@ context 'U2F' do before do sign_in(user) + allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares') end it 'u2f/register.html.raw' do |example| diff --git a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js index 7191fc923ce..69377f8d685 100644 --- a/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js +++ b/spec/javascripts/import_projects/components/provider_repo_table_row_spec.js @@ -49,6 +49,19 @@ describe('ProviderRepoTableRow', () => { expect(vm.$el.querySelector('.js-import-button')).not.toBeNull(); }); + it('renders a select2 namespace select', () => { + vm = createComponent(); + + const dropdownTrigger = vm.$el.querySelector('.js-namespace-select'); + + expect(dropdownTrigger).not.toBeNull(); + expect(dropdownTrigger.classList.contains('select2-container')).toBe(true); + + dropdownTrigger.click(); + + expect(vm.$el.querySelector('.select2-drop')).not.toBeNull(); + }); + it('imports repo when clicking import button', done => { const importPath = '/import-path'; const defaultTargetNamespace = 'user'; diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index c02e37950f8..0bb43c94f6a 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -409,13 +409,6 @@ describe('common_utils', () => { }); }); - describe('convertPermissionToBoolean', () => { - it('should convert a boolean in a string to a boolean', () => { - expect(commonUtils.convertPermissionToBoolean('true')).toEqual(true); - expect(commonUtils.convertPermissionToBoolean('false')).toEqual(false); - }); - }); - describe('backOff', () => { beforeEach(() => { // shortcut our timeouts otherwise these tests will take a long time to finish diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js index d0da659c3d7..138041a349f 100644 --- a/spec/javascripts/lib/utils/poll_spec.js +++ b/spec/javascripts/lib/utils/poll_spec.js @@ -153,6 +153,36 @@ describe('Poll', () => { }); }); + describe('enable', () => { + it('should enable polling upon a response', done => { + jasmine.clock().install(); + + const Polling = new Poll({ + resource: service, + method: 'fetch', + data: { page: 1 }, + successCallback: () => {}, + }); + + Polling.enable({ + data: { page: 4 }, + response: { status: 200, headers: { 'poll-interval': 1 } }, + }); + + jasmine.clock().tick(1); + jasmine.clock().uninstall(); + + waitForAllCallsToFinish(service, 1, () => { + Polling.stop(); + + expect(service.fetch.calls.count()).toEqual(1); + expect(service.fetch).toHaveBeenCalledWith({ page: 4 }); + expect(Polling.options.data).toEqual({ page: 4 }); + done(); + }); + }); + }); + describe('restart', () => { it('should restart polling when its called', done => { mockServiceCall(service, { status: 200, headers: { 'poll-interval': 1 } }); @@ -171,6 +201,7 @@ describe('Poll', () => { }); spyOn(Polling, 'stop').and.callThrough(); + spyOn(Polling, 'enable').and.callThrough(); spyOn(Polling, 'restart').and.callThrough(); Polling.makeRequest(); @@ -181,6 +212,7 @@ describe('Poll', () => { expect(service.fetch.calls.count()).toEqual(2); expect(service.fetch).toHaveBeenCalledWith({ page: 4 }); expect(Polling.stop).toHaveBeenCalled(); + expect(Polling.enable).toHaveBeenCalled(); expect(Polling.restart).toHaveBeenCalled(); expect(Polling.options.data).toEqual({ page: 4 }); done(); diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index 0b36fc9f5f7..d334ef7ba4f 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -127,7 +127,7 @@ describe('Area component', () => { }); it('formats tooltip content', () => { - expect(areaChart.vm.tooltip.content).toBe('CPU (Cores) 5.556'); + expect(areaChart.vm.tooltip.content).toBe('CPU 5.556'); }); }); @@ -213,7 +213,7 @@ describe('Area component', () => { describe('yAxisLabel', () => { it('constructs a label for the chart y-axis', () => { - expect(areaChart.vm.yAxisLabel).toBe('CPU (Cores)'); + expect(areaChart.vm.yAxisLabel).toBe('CPU'); }); }); }); diff --git a/spec/javascripts/notes/components/discussion_filter_note_spec.js b/spec/javascripts/notes/components/discussion_filter_note_spec.js new file mode 100644 index 00000000000..52d2e7ce947 --- /dev/null +++ b/spec/javascripts/notes/components/discussion_filter_note_spec.js @@ -0,0 +1,93 @@ +import Vue from 'vue'; +import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue'; +import eventHub from '~/notes/event_hub'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('DiscussionFilterNote component', () => { + let vm; + + const createComponent = () => { + const Component = Vue.extend(DiscussionFilterNote); + + return mountComponent(Component); + }; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('timelineContent', () => { + it('returns string containing instruction for switching feed type', () => { + expect(vm.timelineContent).toBe( + "You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.", + ); + }); + }); + }); + + describe('methods', () => { + describe('selectFilter', () => { + it('emits `dropdownSelect` event on `eventHub` with provided param', () => { + spyOn(eventHub, '$emit'); + + vm.selectFilter(1); + + expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1); + }); + }); + }); + + describe('template', () => { + it('renders component container element', () => { + expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true); + }); + + it('renders comment icon element', () => { + expect(vm.$el.querySelector('.timeline-icon svg use').getAttribute('xlink:href')).toContain( + 'comment', + ); + }); + + it('renders filter information note', () => { + expect(vm.$el.querySelector('.timeline-content').innerText.trim()).toContain( + "You're only seeing other activity in the feed. To add a comment, switch to one of the following options.", + ); + }); + + it('renders filter buttons', () => { + const buttonsContainerEl = vm.$el.querySelector('.discussion-filter-actions'); + + expect(buttonsContainerEl.querySelector('button:first-child').innerText.trim()).toContain( + 'Show all activity', + ); + + expect(buttonsContainerEl.querySelector('button:last-child').innerText.trim()).toContain( + 'Show comments only', + ); + }); + + it('clicking `Show all activity` button calls `selectFilter("all")` method', () => { + const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:first-child'); + spyOn(vm, 'selectFilter'); + + showAllBtn.dispatchEvent(new Event('click')); + + expect(vm.selectFilter).toHaveBeenCalledWith(0); + }); + + it('clicking `Show comments only` button calls `selectFilter("comments")` method', () => { + const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:last-child'); + spyOn(vm, 'selectFilter'); + + showAllBtn.dispatchEvent(new Event('click')); + + expect(vm.selectFilter).toHaveBeenCalledWith(1); + }); + }); +}); diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js index 91dab58ba7f..1c366aee8e2 100644 --- a/spec/javascripts/notes/components/discussion_filter_spec.js +++ b/spec/javascripts/notes/components/discussion_filter_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import createStore from '~/notes/stores'; import DiscussionFilter from '~/notes/components/discussion_filter.vue'; -import { DISCUSSION_FILTERS_DEFAULT_VALUE } from '~/notes/constants'; +import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants'; import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; import { discussionFiltersMock, discussionMock } from '../mock_data'; @@ -54,14 +54,18 @@ describe('DiscussionFilter component', () => { }); it('updates to the selected item', () => { - const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button'); + const filterItem = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`, + ); filterItem.click(); expect(vm.currentFilter.title).toEqual(filterItem.textContent.trim()); }); it('only updates when selected filter changes', () => { - const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button'); + const filterItem = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`, + ); spyOn(vm, 'filterDiscussion'); filterItem.click(); @@ -70,21 +74,27 @@ describe('DiscussionFilter component', () => { }); it('disables commenting when "Show history only" filter is applied', () => { - const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button'); + const filterItem = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`, + ); filterItem.click(); expect(vm.$store.state.commentsDisabled).toBe(true); }); it('enables commenting when "Show history only" filter is not applied', () => { - const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button'); + const filterItem = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`, + ); filterItem.click(); expect(vm.$store.state.commentsDisabled).toBe(false); }); it('renders a dropdown divider for the default filter', () => { - const defaultFilter = vm.$el.querySelector('.dropdown-menu li:first-child'); + const defaultFilter = vm.$el.querySelector( + `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"]`, + ); expect(defaultFilter.lastChild.classList).toContain('dropdown-divider'); }); diff --git a/spec/javascripts/notes/components/discussion_jump_to_next_button_spec.js b/spec/javascripts/notes/components/discussion_jump_to_next_button_spec.js deleted file mode 100644 index c41b29fa788..00000000000 --- a/spec/javascripts/notes/components/discussion_jump_to_next_button_spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import jumpToNextDiscussionButton from '~/notes/components/discussion_jump_to_next_button.vue'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; - -const localVue = createLocalVue(); - -describe('jumpToNextDiscussionButton', () => { - let wrapper; - - beforeEach(() => { - wrapper = shallowMount(jumpToNextDiscussionButton, { - localVue, - sync: false, - }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('emits onClick event on button click', done => { - const button = wrapper.find({ ref: 'button' }); - - button.trigger('click'); - - localVue.nextTick(() => { - expect(wrapper.emitted()).toEqual({ - onClick: [[]], - }); - - done(); - }); - }); -}); diff --git a/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js b/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js new file mode 100644 index 00000000000..b2a91c9919a --- /dev/null +++ b/spec/javascripts/notes/components/discussion_resolve_with_issue_button_spec.js @@ -0,0 +1,31 @@ +import { GlButton } from '@gitlab/ui'; +import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { TEST_HOST } from 'spec/test_constants'; + +const localVue = createLocalVue(); + +describe('ResolveWithIssueButton', () => { + let wrapper; + const url = `${TEST_HOST}/hello-world/`; + + beforeEach(() => { + wrapper = shallowMount(ResolveWithIssueButton, { + localVue, + sync: false, + propsData: { + url, + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('it should have a link with the provided link property as href', () => { + const button = wrapper.find(GlButton); + + expect(button.attributes().href).toBe(url); + }); +}); diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index d5c0bf6b25d..d716ece3766 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -83,6 +83,8 @@ describe('note_app', () => { describe('render', () => { beforeEach(() => { + setFixtures('<div class="js-discussions-count"></div>'); + Vue.http.interceptors.push(mockData.individualNoteInterceptor); wrapper = mountComponent(); }); @@ -124,9 +126,24 @@ describe('note_app', () => { expect(wrapper.find('.js-main-target-form').exists()).toBe(false); }); + it('should render discussion filter note `commentsDisabled` is true', () => { + store.state.commentsDisabled = true; + wrapper = mountComponent(); + + expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true); + }); + it('should render form comment button as disabled', () => { expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled'); }); + + it('updates discussions badge', done => { + setTimeout(() => { + expect(document.querySelector('.js-discussions-count').textContent).toEqual('2'); + + done(); + }); + }); }); describe('while fetching data', () => { diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index 2eae22e095f..2b93fb9fb45 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -2,6 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import createStore from '~/notes/stores'; import noteableDiscussion from '~/notes/components/noteable_discussion.vue'; import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; +import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue'; import '~/behaviors/markdown/render_gfm'; import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data'; import mockDiffFile from '../../diffs/mock_data/diff_file'; @@ -238,4 +239,42 @@ describe('noteable_discussion component', () => { }); }); }); + + describe('for resolved discussion', () => { + beforeEach(() => { + const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0]; + wrapper.setProps({ discussion }); + }); + + it('does not display a button to resolve with issue', () => { + const button = wrapper.find(ResolveWithIssueButton); + + expect(button.exists()).toBe(false); + }); + }); + + describe('for unresolved discussion', () => { + beforeEach(done => { + const discussion = { + ...getJSONFixture(discussionWithTwoUnresolvedNotes)[0], + expanded: true, + }; + discussion.notes = discussion.notes.map(note => ({ + ...note, + resolved: false, + })); + + wrapper.setProps({ discussion }); + wrapper.vm + .$nextTick() + .then(done) + .catch(done.fail); + }); + + it('displays a button to resolve with issue', () => { + const button = wrapper.find(ResolveWithIssueButton); + + expect(button.exists()).toBe(true); + }); + }); }); diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js index 97ded16db69..78187b69563 100644 --- a/spec/javascripts/pipelines/pipelines_spec.js +++ b/spec/javascripts/pipelines/pipelines_spec.js @@ -446,7 +446,7 @@ describe('Pipelines', () => { }; vm.$nextTick(() => { - vm.$el.querySelector('.js-next-button a').click(); + vm.$el.querySelector('.js-next-button .page-link').click(); expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' }); diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js index e355416bd27..42bf3b7df09 100644 --- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js @@ -6,32 +6,36 @@ import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Deployment component', () => { const Component = Vue.extend(deploymentComponent); - const deploymentMockData = { - id: 15, - name: 'review/diplo', - url: '/root/review-apps/environments/15', - stop_url: '/root/review-apps/environments/15/stop', - metrics_url: '/root/review-apps/environments/15/deployments/1/metrics', - metrics_monitoring_url: '/root/review-apps/environments/15/metrics', - external_url: 'http://gitlab.com.', - external_url_formatted: 'gitlab', - deployed_at: '2017-03-22T22:44:42.258Z', - deployed_at_formatted: 'Mar 22, 2017 10:44pm', - changes: [ - { - path: 'index.html', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', - }, - { - path: 'imgs/gallery.html', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', - }, - { - path: 'about/', - external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', - }, - ], - }; + let deploymentMockData; + + beforeEach(() => { + deploymentMockData = { + id: 15, + name: 'review/diplo', + url: '/root/review-apps/environments/15', + stop_url: '/root/review-apps/environments/15/stop', + metrics_url: '/root/review-apps/environments/15/deployments/1/metrics', + metrics_monitoring_url: '/root/review-apps/environments/15/metrics', + external_url: 'http://gitlab.com.', + external_url_formatted: 'gitlab', + deployed_at: '2017-03-22T22:44:42.258Z', + deployed_at_formatted: 'Mar 22, 2017 10:44pm', + changes: [ + { + path: 'index.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', + }, + { + path: 'imgs/gallery.html', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', + }, + { + path: 'about/', + external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', + }, + ], + }; + }); let vm; @@ -207,6 +211,31 @@ describe('Deployment component', () => { }); }); + describe('with a single change', () => { + beforeEach(() => { + deploymentMockData.changes = deploymentMockData.changes.slice(0, 1); + + vm = mountComponent(Component, { + deployment: { ...deploymentMockData }, + showMetrics: true, + }); + }); + + it('renders the link to the review app without dropdown', () => { + expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull(); + expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull(); + }); + + it('renders the link to the review app linked to to the first change', () => { + const expectedUrl = deploymentMockData.changes[0].external_url; + const deployUrl = vm.$el.querySelector('.js-deploy-url-feature-flag'); + + expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull(); + expect(deployUrl).not.toBeNull(); + expect(deployUrl.href).toEqual(expectedUrl); + }); + }); + describe('deployment status', () => { describe('running', () => { beforeEach(() => { diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js index 0dcb712e720..407d1d59f83 100644 --- a/spec/javascripts/vue_shared/components/table_pagination_spec.js +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js @@ -53,7 +53,7 @@ describe('Pagination component', () => { component.$el.querySelector('.js-previous-button').classList.contains('disabled'), ).toEqual(true); - component.$el.querySelector('.js-previous-button a').click(); + component.$el.querySelector('.js-previous-button .page-link').click(); expect(spy).not.toHaveBeenCalled(); }); @@ -71,7 +71,7 @@ describe('Pagination component', () => { change: spy, }); - component.$el.querySelector('.js-previous-button a').click(); + component.$el.querySelector('.js-previous-button .page-link').click(); expect(spy).toHaveBeenCalledWith(1); }); @@ -91,7 +91,7 @@ describe('Pagination component', () => { change: spy, }); - const button = component.$el.querySelector('.js-first-button a'); + const button = component.$el.querySelector('.js-first-button .page-link'); expect(button.textContent.trim()).toEqual('« First'); @@ -115,7 +115,7 @@ describe('Pagination component', () => { change: spy, }); - const button = component.$el.querySelector('.js-last-button a'); + const button = component.$el.querySelector('.js-last-button .page-link'); expect(button.textContent.trim()).toEqual('Last »'); @@ -141,7 +141,7 @@ describe('Pagination component', () => { expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next'); - component.$el.querySelector('.js-next-button a').click(); + component.$el.querySelector('.js-next-button .page-link').click(); expect(spy).not.toHaveBeenCalled(); }); @@ -159,7 +159,7 @@ describe('Pagination component', () => { change: spy, }); - component.$el.querySelector('.js-next-button a').click(); + component.$el.querySelector('.js-next-button .page-link').click(); expect(spy).toHaveBeenCalledWith(4); }); diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index 2890aa4ae38..6e215ea1561 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' describe API::Helpers::Pagination do let(:resource) { Project.all } + let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:8080/api/v4/projects" } + let(:canonical_api_projects_url) { "#{Gitlab.config.gitlab.url}/api/v4/projects" } subject do Class.new.include(described_class).new @@ -9,13 +11,19 @@ describe API::Helpers::Pagination do describe '#paginate (keyset pagination)' do let(:value) { spy('return value') } + let(:base_query) do + { + pagination: 'keyset', + foo: 'bar', + bar: 'baz' + } + end + let(:query) { base_query } before do - allow(value).to receive(:to_query).and_return(value) - allow(subject).to receive(:header).and_return(value) - allow(subject).to receive(:params).and_return(value) - allow(subject).to receive(:request).and_return(value) + allow(subject).to receive(:params).and_return(query) + allow(subject).to receive(:request).and_return(double(url: "#{incoming_api_projects_url}?#{query.to_query}")) end context 'when resource can be paginated' do @@ -28,10 +36,7 @@ describe API::Helpers::Pagination do end describe 'first page' do - before do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', per_page: 2 }) - end + let(:query) { base_query.merge(per_page: 2) } it 'returns appropriate amount of resources' do expect(subject.paginate(resource).count).to eq 2 @@ -43,7 +48,7 @@ describe API::Helpers::Pagination do it 'adds appropriate headers' do expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[1].id}&pagination=keyset&per_page=2") + expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[1].id).to_query}") expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') @@ -54,10 +59,7 @@ describe API::Helpers::Pagination do end describe 'second page' do - before do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', per_page: 2, ks_prev_id: projects[1].id }) - end + let(:query) { base_query.merge(per_page: 2, ks_prev_id: projects[1].id) } it 'returns appropriate amount of resources' do expect(subject.paginate(resource).count).to eq 1 @@ -69,7 +71,7 @@ describe API::Helpers::Pagination do it 'adds appropriate headers' do expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[2].id}&pagination=keyset&per_page=2") + expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[2].id).to_query}") expect_header('Link', anything) do |_key, val| expect(val).to include('rel="next"') @@ -80,10 +82,7 @@ describe API::Helpers::Pagination do end describe 'third page' do - before do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', per_page: 2, ks_prev_id: projects[2].id }) - end + let(:query) { base_query.merge(per_page: 2, ks_prev_id: projects[2].id) } it 'returns appropriate amount of resources' do expect(subject.paginate(resource).count).to eq 0 @@ -91,6 +90,7 @@ describe API::Helpers::Pagination do it 'adds appropriate headers' do expect_header('X-Per-Page', '2') + expect_no_header('X-Next-Page') expect(subject).not_to receive(:header).with('Link') subject.paginate(resource) @@ -99,10 +99,7 @@ describe API::Helpers::Pagination do context 'if order' do context 'is not present' do - before do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', per_page: 2 }) - end + let(:query) { base_query.merge(per_page: 2) } it 'is not present it adds default order(:id) desc' do resource.order_values = [] @@ -144,9 +141,7 @@ describe API::Helpers::Pagination do # (key is the id) end - it 'it also orders by primary key' do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', per_page: 2 }) + it 'also orders by primary key' do paginated_relation = subject.paginate(resource) expect(paginated_relation.order_values).to be_present @@ -157,46 +152,45 @@ describe API::Helpers::Pagination do expect(paginated_relation.order_values.second.expr.name).to eq :id end - it 'it returns the right records (first page)' do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', per_page: 2 }) + it 'returns the right records (first page)' do result = subject.paginate(resource) expect(result.first).to eq(projects[1]) expect(result.second).to eq(projects[3]) end - it 'it returns the right records (second page)' do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) - result = subject.paginate(resource) + describe 'second page' do + let(:query) { base_query.merge(ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2) } - expect(result.first).to eq(projects[2]) - expect(result.second).to eq(projects[6]) - end + it 'returns the right records (second page)' do + result = subject.paginate(resource) - it 'it returns the right records (third page), note increased per_page' do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', ks_prev_id: projects[6].id, ks_prev_name: projects[6].name, per_page: 5 }) - result = subject.paginate(resource) + expect(result.first).to eq(projects[2]) + expect(result.second).to eq(projects[6]) + end - expect(result.size).to eq(3) - expect(result.first).to eq(projects[0]) - expect(result.second).to eq(projects[4]) - expect(result.last).to eq(projects[5]) + it 'returns the right link to the next page' do + expect_header('X-Per-Page', '2') + expect_header('X-Next-Page', "#{canonical_api_projects_url}?#{query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name).to_query}") + expect_header('Link', anything) do |_key, val| + expect(val).to include('rel="next"') + end + + subject.paginate(resource) + end end - it 'it returns the right link to the next page' do - allow(subject).to receive(:params) - .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) + describe 'third page' do + let(:query) { base_query.merge(ks_prev_id: projects[6].id, ks_prev_name: projects[6].name, per_page: 5) } - expect_header('X-Per-Page', '2') - expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2") - expect_header('Link', anything) do |_key, val| - expect(val).to include('rel="next"') - end + it 'returns the right records (third page), note increased per_page' do + result = subject.paginate(resource) - subject.paginate(resource) + expect(result.size).to eq(3) + expect(result.first).to eq(projects[0]) + expect(result.second).to eq(projects[4]) + expect(result.last).to eq(projects[5]) + end end end end @@ -205,25 +199,13 @@ describe API::Helpers::Pagination do describe '#paginate (default offset-based pagination)' do let(:value) { spy('return value') } + let(:base_query) { { foo: 'bar', bar: 'baz' } } + let(:query) { base_query } before do - allow(value).to receive(:to_query).and_return(value) - allow(subject).to receive(:header).and_return(value) - allow(subject).to receive(:params).and_return(value) - allow(subject).to receive(:request).and_return(value) - end - - describe 'required instance methods' do - let(:return_spy) { spy } - - it 'requires some instance methods' do - expect_message(:header) - expect_message(:params) - expect_message(:request) - - subject.paginate(resource) - end + allow(subject).to receive(:params).and_return(query) + allow(subject).to receive(:request).and_return(double(url: "#{incoming_api_projects_url}?#{query.to_query}")) end context 'when resource can be paginated' do @@ -232,11 +214,6 @@ describe API::Helpers::Pagination do end describe 'first page' do - before do - allow(subject).to receive(:params) - .and_return({ page: 1, per_page: 2 }) - end - shared_examples 'response with pagination headers' do it 'adds appropriate headers' do expect_header('X-Total', '3') @@ -247,9 +224,9 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include('rel="first"') - expect(val).to include('rel="last"') - expect(val).to include('rel="next"') + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="prev"') end @@ -267,6 +244,8 @@ describe API::Helpers::Pagination do end end + let(:query) { base_query.merge(page: 1, per_page: 2) } + context 'when the api_kaminari_count_with_limit feature flag is unset' do it_behaves_like 'paginated response' it_behaves_like 'response with pagination headers' @@ -311,9 +290,9 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include('rel="first"') + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="next")) expect(val).not_to include('rel="last"') - expect(val).to include('rel="next"') expect(val).not_to include('rel="prev"') end @@ -324,10 +303,7 @@ describe API::Helpers::Pagination do end describe 'second page' do - before do - allow(subject).to receive(:params) - .and_return({ page: 2, per_page: 2 }) - end + let(:query) { base_query.merge(page: 2, per_page: 2) } it 'returns appropriate amount of resources' do expect(subject.paginate(resource).count).to eq 1 @@ -342,9 +318,9 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '1') expect_header('Link', anything) do |_key, val| - expect(val).to include('rel="first"') - expect(val).to include('rel="last"') - expect(val).to include('rel="prev"') + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 2).to_query}>; rel="last")) + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="prev")) expect(val).not_to include('rel="next"') end @@ -376,10 +352,7 @@ describe API::Helpers::Pagination do context 'when resource empty' do describe 'first page' do - before do - allow(subject).to receive(:params) - .and_return({ page: 1, per_page: 2 }) - end + let(:query) { base_query.merge(page: 1, per_page: 2) } it 'returns appropriate amount of resources' do expect(subject.paginate(resource).count).to eq 0 @@ -394,8 +367,8 @@ describe API::Helpers::Pagination do expect_header('X-Prev-Page', '') expect_header('Link', anything) do |_key, val| - expect(val).to include('rel="first"') - expect(val).to include('rel="last"') + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="first")) + expect(val).to include(%Q(<#{canonical_api_projects_url}?#{query.merge(page: 1).to_query}>; rel="last")) expect(val).not_to include('rel="prev"') expect(val).not_to include('rel="next"') expect(val).not_to include('page=0') diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index a0270d93d50..43222ddb5e2 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -121,6 +121,42 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do end end + context "youtrack project" do + let(:project) { create(:youtrack_project) } + + before do + project.update!(issues_enabled: false) + end + + context "with right markdown" do + let(:issue) { ExternalIssue.new("YT-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "with underscores in the prefix" do + let(:issue) { ExternalIssue.new("PRJ_1-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "with lowercase letters in the prefix" do + let(:issue) { ExternalIssue.new("YTkPrj-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + + context "with a single-letter prefix" do + let(:issue) { ExternalIssue.new("T-123", project) } + let(:reference) { issue.to_reference } + + it_behaves_like "external issue tracker" + end + end + context "jira project" do let(:project) { create(:jira_project) } let(:reference) { issue.to_reference } diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb index b021e69800a..4f0d57ca8a6 100644 --- a/spec/lib/bitbucket_server/client_spec.rb +++ b/spec/lib/bitbucket_server/client_spec.rb @@ -64,7 +64,7 @@ describe BitbucketServer::Client do let(:url) { "#{base_uri}rest/api/1.0/projects/SOME-PROJECT/repos/my-repo/branches" } it 'requests Bitbucket to create a branch' do - stub_request(:post, url).to_return(status: 204, headers: headers, body: '{}') + stub_request(:post, url).to_return(status: 204, headers: headers, body: nil) subject.create_branch(project, repo_slug, branch, sha) @@ -78,7 +78,7 @@ describe BitbucketServer::Client do let(:url) { "#{base_uri}rest/branch-utils/1.0/projects/SOME-PROJECT/repos/my-repo/branches" } it 'requests Bitbucket to create a branch' do - stub_request(:delete, url).to_return(status: 204, headers: headers, body: '{}') + stub_request(:delete, url).to_return(status: 204, headers: headers, body: nil) subject.delete_branch(project, repo_slug, branch, sha) diff --git a/spec/lib/gitlab/background_migration/digest_column_spec.rb b/spec/lib/gitlab/background_migration/digest_column_spec.rb index 3e107ac3027..a25dcb06005 100644 --- a/spec/lib/gitlab/background_migration/digest_column_spec.rb +++ b/spec/lib/gitlab/background_migration/digest_column_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::BackgroundMigration::DigestColumn, :migration, schema: 20180913 it 'erases token' do expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.to( - change { PersonalAccessToken.find(1).token }.from('token-01').to(nil)) + change { PersonalAccessToken.find(1).read_attribute(:token) }.from('token-01').to(nil)) end end @@ -39,7 +39,7 @@ describe Gitlab::BackgroundMigration::DigestColumn, :migration, schema: 20180913 it 'leaves token empty' do expect { subject.perform(PersonalAccessToken, :token, :token_digest, 1, 2) }.not_to( - change { PersonalAccessToken.find(1).token }.from(nil)) + change { PersonalAccessToken.find(1).read_attribute(:token) }.from(nil)) end end end diff --git a/spec/lib/gitlab/chat/command_spec.rb b/spec/lib/gitlab/chat/command_spec.rb new file mode 100644 index 00000000000..46d23ab2b62 --- /dev/null +++ b/spec/lib/gitlab/chat/command_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Chat::Command do + let(:chat_name) { create(:chat_name) } + + let(:command) do + described_class.new( + project: project, + chat_name: chat_name, + name: 'spinach', + arguments: 'foo', + channel: '123', + response_url: 'http://example.com' + ) + end + + describe '#try_create_pipeline' do + let(:project) { create(:project) } + + it 'returns nil when the command is not valid' do + expect(command) + .to receive(:valid?) + .and_return(false) + + expect(command.try_create_pipeline).to be_nil + end + + it 'tries to create the pipeline when a command is valid' do + expect(command) + .to receive(:valid?) + .and_return(true) + + expect(command) + .to receive(:create_pipeline) + + command.try_create_pipeline + end + end + + describe '#create_pipeline' do + let(:project) { create(:project, :test_repo) } + let(:pipeline) { command.create_pipeline } + + before do + stub_repository_ci_yaml_file(sha: project.commit.id) + + project.add_developer(chat_name.user) + end + + it 'creates the pipeline' do + expect(pipeline).to be_persisted + end + + it 'creates the chat data for the pipeline' do + expect(pipeline.chat_data).to be_an_instance_of(Ci::PipelineChatData) + end + + it 'stores the chat name ID in the chat data' do + expect(pipeline.chat_data.chat_name_id).to eq(chat_name.id) + end + + it 'stores the response URL in the chat data' do + expect(pipeline.chat_data.response_url).to eq('http://example.com') + end + + it 'creates the environment variables for the pipeline' do + vars = pipeline.variables.each_with_object({}) do |row, hash| + hash[row.key] = row.value + end + + expect(vars['CHAT_INPUT']).to eq('foo') + expect(vars['CHAT_CHANNEL']).to eq('123') + end + end +end diff --git a/spec/lib/gitlab/chat/output_spec.rb b/spec/lib/gitlab/chat/output_spec.rb new file mode 100644 index 00000000000..b179f9e9d0a --- /dev/null +++ b/spec/lib/gitlab/chat/output_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Chat::Output do + let(:build) do + create(:ci_build, pipeline: create(:ci_pipeline, source: :chat)) + end + + let(:output) { described_class.new(build) } + + describe '#to_s' do + it 'returns the build output as a String' do + trace = Gitlab::Ci::Trace.new(build) + + trace.set("echo hello\nhello") + + allow(build) + .to receive(:trace) + .and_return(trace) + + allow(output) + .to receive(:read_offset_and_length) + .and_return([0, 13]) + + expect(output.to_s).to eq('he') + end + end + + describe '#read_offset_and_length' do + context 'without the chat_reply trace section' do + it 'falls back to using the build_script trace section' do + expect(output) + .to receive(:find_build_trace_section) + .with('chat_reply') + .and_return(nil) + + expect(output) + .to receive(:find_build_trace_section) + .with('build_script') + .and_return({ name: 'build_script', byte_start: 1, byte_end: 4 }) + + expect(output.read_offset_and_length).to eq([1, 3]) + end + end + + context 'without the build_script trace section' do + it 'raises MissingBuildSectionError' do + expect { output.read_offset_and_length } + .to raise_error(described_class::MissingBuildSectionError) + end + end + + context 'with the chat_reply trace section' do + it 'returns the read offset and length as an Array' do + trace = Gitlab::Ci::Trace.new(build) + + allow(build) + .to receive(:trace) + .and_return(trace) + + allow(trace) + .to receive(:extract_sections) + .and_return([{ name: 'chat_reply', byte_start: 1, byte_end: 4 }]) + + expect(output.read_offset_and_length).to eq([1, 3]) + end + end + end + + describe '#without_executed_command_line' do + it 'returns the input without the first line' do + expect(output.without_executed_command_line("hello\nworld")) + .to eq('world') + end + + it 'returns an empty String when the input is empty' do + expect(output.without_executed_command_line('')).to eq('') + end + + it 'returns an empty String when the input consits of a single newline' do + expect(output.without_executed_command_line("\n")).to eq('') + end + end + + describe '#find_build_trace_section' do + it 'returns nil when no section could be found' do + expect(output.find_build_trace_section('foo')).to be_nil + end + + it 'returns the trace section when it could be found' do + section = { name: 'chat_reply', byte_start: 1, byte_end: 4 } + + allow(output) + .to receive(:trace_sections) + .and_return([section]) + + expect(output.find_build_trace_section('chat_reply')).to eq(section) + end + end +end diff --git a/spec/lib/gitlab/chat/responder/base_spec.rb b/spec/lib/gitlab/chat/responder/base_spec.rb new file mode 100644 index 00000000000..7fa9bad9d38 --- /dev/null +++ b/spec/lib/gitlab/chat/responder/base_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Chat::Responder::Base do + let(:project) { double(:project) } + let(:pipeline) { double(:pipeline, project: project) } + let(:build) { double(:build, pipeline: pipeline) } + let(:responder) { described_class.new(build) } + + describe '#pipeline' do + it 'returns the pipeline' do + expect(responder.pipeline).to eq(pipeline) + end + end + + describe '#project' do + it 'returns the project' do + expect(responder.project).to eq(project) + end + end + + describe '#success' do + it 'raises NotImplementedError' do + expect { responder.success }.to raise_error(NotImplementedError) + end + end + + describe '#failure' do + it 'raises NotImplementedError' do + expect { responder.failure }.to raise_error(NotImplementedError) + end + end + + describe '#send_response' do + it 'raises NotImplementedError' do + expect { responder.send_response('hello') } + .to raise_error(NotImplementedError) + end + end + + describe '#scheduled_output' do + it 'raises NotImplementedError' do + expect { responder.scheduled_output } + .to raise_error(NotImplementedError) + end + end +end diff --git a/spec/lib/gitlab/chat/responder/slack_spec.rb b/spec/lib/gitlab/chat/responder/slack_spec.rb new file mode 100644 index 00000000000..a1553232b32 --- /dev/null +++ b/spec/lib/gitlab/chat/responder/slack_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Chat::Responder::Slack do + let(:chat_name) { create(:chat_name, chat_id: 'U123') } + + let(:pipeline) do + pipeline = create(:ci_pipeline) + + pipeline.create_chat_data!( + response_url: 'http://example.com', + chat_name_id: chat_name.id + ) + + pipeline + end + + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:responder) { described_class.new(build) } + + describe '#send_response' do + it 'sends a response back to Slack' do + expect(Gitlab::HTTP).to receive(:post).with( + 'http://example.com', + { headers: { Accept: 'application/json' }, body: 'hello'.to_json } + ) + + responder.send_response('hello') + end + end + + describe '#success' do + it 'returns the output for a successful build' do + expect(responder) + .to receive(:send_response) + .with(hash_including(text: /<@U123>:.+hello/, response_type: :in_channel)) + + responder.success('hello') + end + + it 'limits the output to a fixed size' do + expect(responder) + .to receive(:send_response) + .with(hash_including(text: /The output is too large/)) + + responder.success('a' * 4000) + end + + it 'does not send a response if the output is empty' do + expect(responder).not_to receive(:send_response) + + responder.success('') + end + end + + describe '#failure' do + it 'returns the output for a failed build' do + expect(responder).to receive(:send_response).with( + hash_including( + text: /<@U123>:.+Sorry, the build failed!/, + response_type: :in_channel + ) + ) + + responder.failure + end + end + + describe '#scheduled_output' do + it 'returns the output for a scheduled build' do + output = responder.scheduled_output + + expect(output).to eq({ text: '' }) + end + end +end diff --git a/spec/lib/gitlab/chat/responder_spec.rb b/spec/lib/gitlab/chat/responder_spec.rb new file mode 100644 index 00000000000..9893689cba9 --- /dev/null +++ b/spec/lib/gitlab/chat/responder_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Chat::Responder do + describe '.responder_for' do + context 'using a regular build' do + it 'returns nil' do + build = create(:ci_build) + + expect(described_class.responder_for(build)).to be_nil + end + end + + context 'using a chat build' do + it 'returns the responder for the build' do + pipeline = create(:ci_pipeline) + build = create(:ci_build, pipeline: pipeline) + service = double(:service, chat_responder: Gitlab::Chat::Responder::Slack) + chat_name = double(:chat_name, service: service) + chat_data = double(:chat_data, chat_name: chat_name) + + allow(pipeline) + .to receive(:chat_data) + .and_return(chat_data) + + expect(described_class.responder_for(build)) + .to be_an_instance_of(Gitlab::Chat::Responder::Slack) + end + end + end +end diff --git a/spec/lib/gitlab/chat_spec.rb b/spec/lib/gitlab/chat_spec.rb new file mode 100644 index 00000000000..d61c4b36668 --- /dev/null +++ b/spec/lib/gitlab/chat_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::Chat, :use_clean_rails_memory_store_caching do + describe '.available?' do + it 'returns true when the chatops feature is available' do + allow(Feature) + .to receive(:enabled?) + .with(:chatops, default_enabled: true) + .and_return(true) + + expect(described_class).to be_available + end + + it 'returns false when the chatops feature is not available' do + allow(Feature) + .to receive(:enabled?) + .with(:chatops, default_enabled: true) + .and_return(false) + + expect(described_class).not_to be_available + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb index fab071405df..c9d1d09a938 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -101,6 +101,8 @@ describe Gitlab::Ci::Pipeline::Chain::Build do checkout_sha: project.commit.id, after_sha: nil, before_sha: nil, + source_sha: merge_request.diff_head_sha, + target_sha: merge_request.target_branch_sha, trigger_request: nil, schedule: nil, merge_request: merge_request, @@ -118,5 +120,10 @@ describe Gitlab::Ci::Pipeline::Chain::Build do expect(pipeline).to be_merge_request expect(pipeline.merge_request).to eq(merge_request) end + + it 'correctly sets souce sha and target sha to pipeline' do + expect(pipeline.source_sha).to eq(merge_request.diff_head_sha) + expect(pipeline.target_sha).to eq(merge_request.target_branch_sha) + end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb index 6aa802ce6fd..dab0fb51bcc 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -161,6 +161,54 @@ describe Gitlab::Ci::Pipeline::Chain::Command do end end + describe '#source_sha' do + subject { command.source_sha } + + let(:command) do + described_class.new(project: project, + source_sha: source_sha, + merge_request: merge_request) + end + + let(:merge_request) do + create(:merge_request, target_project: project, source_project: project) + end + + let(:source_sha) { nil } + + context 'when source_sha is specified' do + let(:source_sha) { 'abc' } + + it 'returns the specified value' do + is_expected.to eq('abc') + end + end + end + + describe '#target_sha' do + subject { command.target_sha } + + let(:command) do + described_class.new(project: project, + target_sha: target_sha, + merge_request: merge_request) + end + + let(:merge_request) do + create(:merge_request, target_project: project, source_project: project) + end + + let(:target_sha) { nil } + + context 'when target_sha is specified' do + let(:target_sha) { 'abc' } + + it 'returns the specified value' do + is_expected.to eq('abc') + end + end + end + describe '#protected_ref?' do let(:command) { described_class.new(project: project, origin_ref: 'my-branch') } diff --git a/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb new file mode 100644 index 00000000000..7c1c016b4bb --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline/chain/remove_unwanted_chat_jobs_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do + let(:project) { create(:project, :repository) } + + let(:pipeline) do + build(:ci_pipeline_with_one_job, project: project, ref: 'master') + end + + let(:command) do + double(:command, project: project, chat_data: { command: 'echo' }) + end + + describe '#perform!' do + it 'removes unwanted jobs for chat pipelines' do + allow(pipeline).to receive(:chat?).and_return(true) + + pipeline.config_processor.jobs[:echo] = double(:job) + + described_class.new(pipeline, command).perform! + + expect(pipeline.config_processor.jobs.keys).to eq([:echo]) + end + end + + it 'does not remove any jobs for non-chat pipelines' do + described_class.new(pipeline, command).perform! + + expect(pipeline.config_processor.jobs.keys).to eq([:rspec]) + end +end diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index 793cac593a2..00cb1e6446a 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -217,6 +217,7 @@ describe Gitlab::Danger::Helper do 'app/views/foo' | :frontend 'public/foo' | :frontend 'spec/javascripts/foo' | :frontend + 'spec/frontend/bar' | :frontend 'vendor/assets/foo' | :frontend 'jest.config.js' | :frontend 'package.json' | :frontend @@ -225,6 +226,7 @@ describe Gitlab::Danger::Helper do 'ee/app/assets/foo' | :frontend 'ee/app/views/foo' | :frontend 'ee/spec/javascripts/foo' | :frontend + 'ee/spec/frontend/bar' | :frontend 'app/models/foo' | :backend 'bin/foo' | :backend diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 8a9e78ba3c3..b3a728c139e 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1704,6 +1704,37 @@ describe Gitlab::Git::Repository, :seed_helper do end end + describe '#merge_to_ref' do + let(:repository) { mutable_repository } + let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } + let(:left_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' } + let(:right_branch) { 'test-master' } + let(:target_ref) { 'refs/merge-requests/999/merge' } + + before do + repository.create_branch(right_branch, branch_head) unless repository.branch_exists?(right_branch) + end + + def merge_to_ref + repository.merge_to_ref(user, left_sha, right_branch, target_ref, 'Merge message') + end + + it 'generates a commit in the target_ref' do + expect(repository.ref_exists?(target_ref)).to be(false) + + commit_sha = merge_to_ref + ref_head = repository.commit(target_ref) + + expect(commit_sha).to be_present + expect(repository.ref_exists?(target_ref)).to be(true) + expect(ref_head.id).to eq(commit_sha) + end + + it 'does not change the right branch HEAD' do + expect { merge_to_ref }.not_to change { repository.find_branch(right_branch).target } + end + end + describe '#merge' do let(:repository) { mutable_repository } let(:source_sha) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' } diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb index 95bf7685ade..13cf52fd795 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb @@ -100,4 +100,22 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do expect { fake_class.new.find_object }.to raise_error(/Implement #find_object in #{fake_class.name}/) end end + + describe '#authorize' do + it 'adds permissions from subclasses to those of superclasses when used on classes' do + base_class = Class.new do + include Gitlab::Graphql::Authorize::AuthorizeResource + + authorize :base_authorization + end + + sub_class = Class.new(base_class) do + authorize :sub_authorization + end + + expect(base_class.required_permissions).to contain_exactly(:base_authorization) + expect(sub_class.required_permissions) + .to contain_exactly(:base_authorization, :sub_authorization) + end + end end diff --git a/spec/lib/gitlab/graphql/authorize_spec.rb b/spec/lib/gitlab/graphql/authorize_spec.rb deleted file mode 100644 index 9c17a3b0e4b..00000000000 --- a/spec/lib/gitlab/graphql/authorize_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Graphql::Authorize do - describe '#authorize' do - it 'adds permissions from subclasses to those of superclasses when used on classes' do - base_class = Class.new do - extend Gitlab::Graphql::Authorize - - authorize :base_authorization - end - sub_class = Class.new(base_class) do - authorize :sub_authorization - end - - expect(base_class.required_permissions).to contain_exactly(:base_authorization) - expect(sub_class.required_permissions) - .to contain_exactly(:base_authorization, :sub_authorization) - end - end -end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index c15b360b563..018a5d3dd3d 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -131,6 +131,7 @@ ci_pipelines: - merge_request - deployments - environments +- chat_data pipeline_variables: - pipeline stages: @@ -232,6 +233,7 @@ project: - pushover_service - jira_service - redmine_service +- youtrack_service - custom_issue_tracker_service - bugzilla_service - gitlab_issue_tracker_service diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 1327f414498..773651dd226 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6630,6 +6630,26 @@ "deploy_keys": [], "services": [ { + "id": 101, + "title": "YouTrack", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.327Z", + "updated_at": "2016-06-14T15:01:51.327Z", + "active": false, + "properties": {}, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "job_events": true, + "type": "YoutrackService", + "category": "issue_tracker", + "default": false, + "wiki_page_events": true + }, + { "id": 100, "title": "JetBrains TeamCity CI", "project_id": 5, diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 46fdfba953b..cfc3e0ce926 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -78,6 +78,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver do expect(saved_project_json['releases']).not_to be_empty end + it 'has no author on releases' do + expect(saved_project_json['releases'].first['author']).to be_nil + end + + it 'has the author ID on releases' do + expect(saved_project_json['releases'].first['author_id']).not_to be_nil + end + it 'has issues' do expect(saved_project_json['issues']).not_to be_empty end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index baca8f6d542..ee96e5c4d42 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -235,6 +235,8 @@ Ci::Pipeline: - ref - sha - before_sha +- source_sha +- target_sha - push_data - created_at - updated_at diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb index 42635a68ee1..6ec86163233 100644 --- a/spec/lib/gitlab/issuable_metadata_spec.rb +++ b/spec/lib/gitlab/issuable_metadata_spec.rb @@ -28,12 +28,12 @@ describe Gitlab::IssuableMetadata do expect(data.count).to eq(2) expect(data[issue.id].upvotes).to eq(1) expect(data[issue.id].downvotes).to eq(0) - expect(data[issue.id].notes_count).to eq(0) + expect(data[issue.id].user_notes_count).to eq(0) expect(data[issue.id].merge_requests_count).to eq(1) expect(data[closed_issue.id].upvotes).to eq(0) expect(data[closed_issue.id].downvotes).to eq(1) - expect(data[closed_issue.id].notes_count).to eq(0) + expect(data[closed_issue.id].user_notes_count).to eq(0) expect(data[closed_issue.id].merge_requests_count).to eq(0) end end @@ -51,12 +51,12 @@ describe Gitlab::IssuableMetadata do expect(data.count).to eq(2) expect(data[merge_request.id].upvotes).to eq(1) expect(data[merge_request.id].downvotes).to eq(1) - expect(data[merge_request.id].notes_count).to eq(1) + expect(data[merge_request.id].user_notes_count).to eq(1) expect(data[merge_request.id].merge_requests_count).to eq(0) expect(data[merge_request_closed.id].upvotes).to eq(0) expect(data[merge_request_closed.id].downvotes).to eq(0) - expect(data[merge_request_closed.id].notes_count).to eq(0) + expect(data[merge_request_closed.id].user_notes_count).to eq(0) expect(data[merge_request_closed.id].merge_requests_count).to eq(0) end end diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb index 27c802f34ec..95b6b3fd953 100644 --- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Kubernetes::Helm::Pod do it 'should generate the appropriate specifications for the container' do container = subject.generate.spec.containers.first expect(container.name).to eq('helm') - expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.2-kube-1.11.0') + expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7') expect(container.env.count).to eq(3) expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT]) expect(container.command).to match_array(["/bin/sh"]) diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb index 8bb0c1a0b8a..9f2214f7ce7 100644 --- a/spec/lib/gitlab/profiler_spec.rb +++ b/spec/lib/gitlab/profiler_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Gitlab::Profiler do - RSpec::Matchers.define_negated_matcher :not_change, :change - let(:null_logger) { Logger.new('/dev/null') } let(:private_token) { 'private' } @@ -187,7 +185,7 @@ describe Gitlab::Profiler do end it 'does not modify the standard Rails loggers' do - expect { described_class.with_custom_logger(nil) { } } + expect { described_class.with_custom_logger(nil) {} } .to not_change { ActiveRecord::Base.logger } .and not_change { ActionController::Base.logger } .and not_change { ActiveSupport::LogSubscriber.colorize_logging } @@ -204,7 +202,7 @@ describe Gitlab::Profiler do end it 'cleans up ApplicationController afterwards' do - expect { described_class.with_user(user) { } } + expect { described_class.with_user(user) {} } .to not_change { ActionController.instance_methods(false) } end end @@ -213,7 +211,7 @@ describe Gitlab::Profiler do it 'does not define methods on ApplicationController' do expect(ApplicationController).not_to receive(:define_method) - described_class.with_user(nil) { } + described_class.with_user(nil) {} end end end diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb index 1cc2bde50e9..0cee100b64e 100644 --- a/spec/lib/gitlab/project_template_spec.rb +++ b/spec/lib/gitlab/project_template_spec.rb @@ -7,11 +7,17 @@ describe Gitlab::ProjectTemplate do described_class.new('rails', 'Ruby on Rails', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/rails'), described_class.new('spring', 'Spring', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/spring'), described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express'), + described_class.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'), described_class.new('hugo', 'Pages/Hugo', 'Everything you need to get started using a Hugo Pages site.', 'https://gitlab.com/pages/hugo'), described_class.new('jekyll', 'Pages/Jekyll', 'Everything you need to get started using a Jekyll Pages site.', 'https://gitlab.com/pages/jekyll'), described_class.new('plainhtml', 'Pages/Plain HTML', 'Everything you need to get started using a plain HTML Pages site.', 'https://gitlab.com/pages/plain-html'), described_class.new('gitbook', 'Pages/GitBook', 'Everything you need to get started using a GitBook Pages site.', 'https://gitlab.com/pages/gitbook'), - described_class.new('hexo', 'Pages/Hexo', 'Everything you need to get started using a plan Hexo Pages site.', 'https://gitlab.com/pages/hexo') + described_class.new('hexo', 'Pages/Hexo', 'Everything you need to get started using a Hexo Pages site.', 'https://gitlab.com/pages/hexo'), + described_class.new('nfhugo', 'Netlify/Hugo', _('A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhugo'), + described_class.new('nfjekyll', 'Netlify/Jekyll', _('A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfjekyll'), + described_class.new('nfplainhtml', 'Netlify/Plain HTML', _('A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfplain-html'), + described_class.new('nfgitbook', 'Netlify/GitBook', _('A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfgitbook'), + described_class.new('nfhexo', 'Netlify/Hexo', _('A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features.'), 'https://gitlab.com/pages/nfhexo') ] expect(described_class.all).to be_an(Array) diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb index 1bc6536439e..c54be78f050 100644 --- a/spec/lib/gitlab/serializer/pagination_spec.rb +++ b/spec/lib/gitlab/serializer/pagination_spec.rb @@ -1,16 +1,12 @@ require 'spec_helper' describe Gitlab::Serializer::Pagination do - let(:request) { spy('request') } + let(:request) { double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query) } let(:response) { spy('response') } let(:headers) { spy('headers') } before do - allow(request).to receive(:query_parameters) - .and_return(params) - - allow(response).to receive(:headers) - .and_return(headers) + allow(response).to receive(:headers).and_return(headers) end let(:pagination) { described_class.new(request, response) } @@ -19,7 +15,7 @@ describe Gitlab::Serializer::Pagination do subject { pagination.paginate(resource) } let(:resource) { User.all } - let(:params) { { page: 1, per_page: 2 } } + let(:query) { { page: 1, per_page: 2 } } context 'when a multiple resources are present in relation' do before do diff --git a/spec/lib/gitlab/slash_commands/application_help_spec.rb b/spec/lib/gitlab/slash_commands/application_help_spec.rb new file mode 100644 index 00000000000..b203a1ee79c --- /dev/null +++ b/spec/lib/gitlab/slash_commands/application_help_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SlashCommands::ApplicationHelp do + let(:params) { { command: '/gitlab', text: 'help' } } + + describe '#execute' do + subject do + described_class.new(params).execute + end + + it 'displays the help section' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:text]).to include('Available commands') + expect(subject[:text]).to include('/gitlab [project name or alias] issue show') + end + end +end diff --git a/spec/lib/gitlab/slash_commands/presenters/error_spec.rb b/spec/lib/gitlab/slash_commands/presenters/error_spec.rb new file mode 100644 index 00000000000..30ff81510c1 --- /dev/null +++ b/spec/lib/gitlab/slash_commands/presenters/error_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SlashCommands::Presenters::Error do + subject { described_class.new('Error').message } + + it { is_expected.to be_a(Hash) } + + it 'shows the error message' do + expect(subject[:response_type]).to be(:ephemeral) + expect(subject[:status]).to eq(200) + expect(subject[:text]).to eq('Error') + end +end diff --git a/spec/lib/gitlab/slash_commands/presenters/run_spec.rb b/spec/lib/gitlab/slash_commands/presenters/run_spec.rb new file mode 100644 index 00000000000..f3ab01ef6bb --- /dev/null +++ b/spec/lib/gitlab/slash_commands/presenters/run_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SlashCommands::Presenters::Run do + let(:presenter) { described_class.new } + + describe '#present' do + context 'when no builds are present' do + it 'returns an error' do + builds = double(:builds, take: nil) + pipeline = double(:pipeline, builds: builds) + + expect(presenter) + .to receive(:unsupported_chat_service) + + presenter.present(pipeline) + end + end + + context 'when a responder could be found' do + it 'returns the output for a scheduled pipeline' do + responder = double(:responder, scheduled_output: 'hello') + build = double(:build) + builds = double(:builds, take: build) + pipeline = double(:pipeline, builds: builds) + + allow(Gitlab::Chat::Responder) + .to receive(:responder_for) + .with(build) + .and_return(responder) + + expect(presenter) + .to receive(:in_channel_response) + .with('hello') + + presenter.present(pipeline) + end + end + + context 'when a responder could not be found' do + it 'returns an error' do + build = double(:build) + builds = double(:builds, take: build) + pipeline = double(:pipeline, builds: builds) + + allow(Gitlab::Chat::Responder) + .to receive(:responder_for) + .with(build) + .and_return(nil) + + expect(presenter) + .to receive(:unsupported_chat_service) + + presenter.present(pipeline) + end + end + end + + describe '#unsupported_chat_service' do + it 'returns an ephemeral response' do + expect(presenter) + .to receive(:ephemeral_response) + .with(text: /Sorry, this chat service is currently not supported/) + + presenter.unsupported_chat_service + end + end + + describe '#failed_to_schedule' do + it 'returns an ephemeral response' do + expect(presenter) + .to receive(:ephemeral_response) + .with(text: /The command could not be scheduled/) + + presenter.failed_to_schedule('foo') + end + end +end diff --git a/spec/lib/gitlab/slash_commands/run_spec.rb b/spec/lib/gitlab/slash_commands/run_spec.rb new file mode 100644 index 00000000000..900fae05719 --- /dev/null +++ b/spec/lib/gitlab/slash_commands/run_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SlashCommands::Run do + describe '.available?' do + it 'returns true when builds are enabled for the project' do + project = double(:project, builds_enabled?: true) + + allow(Gitlab::Chat) + .to receive(:available?) + .and_return(true) + + expect(described_class.available?(project)).to eq(true) + end + + it 'returns false when builds are disabled for the project' do + project = double(:project, builds_enabled?: false) + + expect(described_class.available?(project)).to eq(false) + end + + it 'returns false when chatops is not available' do + allow(Gitlab::Chat) + .to receive(:available?) + .and_return(false) + + project = double(:project, builds_enabled?: true) + + expect(described_class.available?(project)).to eq(false) + end + end + + describe '.allowed?' do + it 'returns true when the user can create a pipeline' do + project = create(:project) + + expect(described_class.allowed?(project, project.creator)).to eq(true) + end + + it 'returns false when the user can not create a pipeline' do + project = create(:project) + user = create(:user) + + expect(described_class.allowed?(project, user)).to eq(false) + end + end + + describe '#execute' do + let(:chat_name) { create(:chat_name) } + let(:project) { create(:project) } + + let(:command) do + described_class.new(project, chat_name, response_url: 'http://example.com') + end + + context 'when a pipeline could not be scheduled' do + it 'returns an error' do + expect_any_instance_of(Gitlab::Chat::Command) + .to receive(:try_create_pipeline) + .and_return(nil) + + expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run) + .to receive(:failed_to_schedule) + .with('foo') + + command.execute(command: 'foo', arguments: '') + end + end + + context 'when a pipeline could be created but the chat service was not supported' do + it 'returns an error' do + build = double(:build) + pipeline = double( + :pipeline, + builds: double(:relation, take: build), + persisted?: true + ) + + expect_any_instance_of(Gitlab::Chat::Command) + .to receive(:try_create_pipeline) + .and_return(pipeline) + + expect(Gitlab::Chat::Responder) + .to receive(:responder_for) + .with(build) + .and_return(nil) + + expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run) + .to receive(:unsupported_chat_service) + + command.execute(command: 'foo', arguments: '') + end + end + + context 'using a valid pipeline' do + it 'schedules the pipeline' do + responder = double(:responder, scheduled_output: 'hello') + build = double(:build) + pipeline = double( + :pipeline, + builds: double(:relation, take: build), + persisted?: true + ) + + expect_any_instance_of(Gitlab::Chat::Command) + .to receive(:try_create_pipeline) + .and_return(pipeline) + + expect(Gitlab::Chat::Responder) + .to receive(:responder_for) + .with(build) + .and_return(responder) + + expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run) + .to receive(:in_channel_response) + .with(responder.scheduled_output) + + command.execute(command: 'foo', arguments: '') + end + end + end +end diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb index 2cddc895026..566b5050e47 100644 --- a/spec/lib/gitlab/tracing_spec.rb +++ b/spec/lib/gitlab/tracing_spec.rb @@ -44,12 +44,15 @@ describe Gitlab::Tracing do describe '.tracing_url' do where(:tracing_url_enabled?, :tracing_url_template, :correlation_id, :process_name, :tracing_url) do - false | "https://localhost" | "123" | "web" | nil - true | "https://localhost" | "123" | "web" | "https://localhost" - true | "https://localhost?service=%{service}" | "123" | "web" | "https://localhost?service=web" - true | "https://localhost?c=%{correlation_id}" | "123" | "web" | "https://localhost?c=123" - true | "https://localhost?c=%{correlation_id}&s=%{service}" | "123" | "web" | "https://localhost?c=123&s=web" - true | "https://localhost?c=%{correlation_id}" | nil | "web" | "https://localhost?c=" + false | "https://localhost" | "123" | "web" | nil + true | "https://localhost" | "123" | "web" | "https://localhost" + true | "https://localhost?service={{ service }}" | "123" | "web" | "https://localhost?service=web" + true | "https://localhost?c={{ correlation_id }}" | "123" | "web" | "https://localhost?c=123" + true | "https://localhost?c={{ correlation_id }}&s={{ service }}" | "123" | "web" | "https://localhost?c=123&s=web" + true | "https://localhost?c={{ correlation_id }}" | nil | "web" | "https://localhost?c=" + true | "https://localhost?c={{ correlation_id }}&s=%22{{ service }}%22" | "123" | "web" | "https://localhost?c=123&s=%22web%22" + true | "https://localhost?c={{correlation_id}}&s={{service}}" | "123" | "web" | "https://localhost?c=123&s=web" + true | "https://localhost?c={{correlation_id }}&s={{ service}}" | "123" | "web" | "https://localhost?c=123&s=web" end with_them do diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index 6fbf60a6222..88e7e2e5ebb 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -36,7 +36,7 @@ describe Sentry::Client do end it 'does not follow redirects' do - expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response error: 302') + expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response status code: 302') expect(redirect_req_stub).to have_been_requested expect(redirected_req_stub).not_to have_been_requested end diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index cc76a2019ec..28d482adebf 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -63,4 +63,19 @@ describe Appearance do %i(logo header_logo favicon).each do |logo_type| it_behaves_like 'logo paths', logo_type end + + describe 'validations' do + let(:triplet) { '#000' } + let(:hex) { '#AABBCC' } + + it { is_expected.to allow_value(nil).for(:message_background_color) } + it { is_expected.to allow_value(triplet).for(:message_background_color) } + it { is_expected.to allow_value(hex).for(:message_background_color) } + it { is_expected.not_to allow_value('000').for(:message_background_color) } + + it { is_expected.to allow_value(nil).for(:message_font_color) } + it { is_expected.to allow_value(triplet).for(:message_font_color) } + it { is_expected.to allow_value(hex).for(:message_font_color) } + it { is_expected.not_to allow_value('000').for(:message_font_color) } + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 47865e4d08f..17540443688 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -23,6 +23,7 @@ describe Ci::Build do it { is_expected.to validate_presence_of(:ref) } it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:trace) } + it { is_expected.to delegate_method(:merge_request?).to(:pipeline) } it { is_expected.to be_a(ArtifactMigratable) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 460b5c8cd31..ee400bec04b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Ci::Pipeline, :mailer do + include ProjectForksHelper + let(:user) { create(:user) } set(:project) { create(:project) } @@ -22,6 +24,7 @@ describe Ci::Pipeline, :mailer do it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:auto_canceled_pipelines) } it { is_expected.to have_many(:auto_canceled_jobs) } + it { is_expected.to have_one(:chat_data) } it { is_expected.to validate_presence_of(:sha) } it { is_expected.to validate_presence_of(:status) } @@ -127,6 +130,132 @@ describe Ci::Pipeline, :mailer do end end + describe '.detached_merge_request_pipelines' do + subject { described_class.detached_merge_request_pipelines(merge_request) } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { nil } + + it 'returns detached merge request pipelines' do + is_expected.to eq([pipeline]) + end + + context 'when target sha exists' do + let(:target_sha) { merge_request.target_branch_sha } + + it 'returns empty array' do + is_expected.to be_empty + end + end + end + + describe '#detached_merge_request_pipeline?' do + subject { pipeline.detached_merge_request_pipeline? } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { nil } + + it { is_expected.to be_truthy } + + context 'when target sha exists' do + let(:target_sha) { merge_request.target_branch_sha } + + it { is_expected.to be_falsy } + end + end + + describe '.merge_request_pipelines' do + subject { described_class.merge_request_pipelines(merge_request) } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { merge_request.target_branch_sha } + + it 'returns merge pipelines' do + is_expected.to eq([pipeline]) + end + + context 'when target sha is empty' do + let(:target_sha) { nil } + + it 'returns empty array' do + is_expected.to be_empty + end + end + end + + describe '#merge_request_pipeline?' do + subject { pipeline.merge_request_pipeline? } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { merge_request.target_branch_sha } + + it { is_expected.to be_truthy } + + context 'when target sha is empty' do + let(:target_sha) { nil } + + it { is_expected.to be_falsy } + end + end + + describe '.mergeable_merge_request_pipelines' do + subject { described_class.mergeable_merge_request_pipelines(merge_request) } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { merge_request.target_branch_sha } + + it 'returns mergeable merge pipelines' do + is_expected.to eq([pipeline]) + end + + context 'when target sha does not point the head of the target branch' do + let(:target_sha) { merge_request.diff_head_sha } + + it 'returns empty array' do + is_expected.to be_empty + end + end + end + + describe '#mergeable_merge_request_pipeline?' do + subject { pipeline.mergeable_merge_request_pipeline? } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request, merge_request: merge_request, target_sha: target_sha) + end + + let(:merge_request) { create(:merge_request) } + let(:target_sha) { merge_request.target_branch_sha } + + it { is_expected.to be_truthy } + + context 'when target sha does not point the head of the target branch' do + let(:target_sha) { merge_request.diff_head_sha } + + it { is_expected.to be_falsy } + end + end + describe '.merge_request' do subject { described_class.merge_request } @@ -397,10 +526,12 @@ describe Ci::Pipeline, :mailer do 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path, 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url, 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s, + 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA' => pipeline.target_sha.to_s, 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s, 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path, 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url, - 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s) + 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s, + 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s) end context 'when source project does not exist' do @@ -2113,68 +2244,83 @@ describe Ci::Pipeline, :mailer do describe "#all_merge_requests" do let(:project) { create(:project) } - let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master') } - - it "returns all merge requests having the same source branch" do - merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref) - - expect(pipeline.all_merge_requests).to eq([merge_request]) - end - it "doesn't return merge requests having a different source branch" do - create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') - - expect(pipeline.all_merge_requests).to be_empty - end + shared_examples 'a method that returns all merge requests for a given pipeline' do + let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: pipeline_project, ref: 'master') } - context 'when there is a merge request pipeline' do - let(:source_branch) { 'feature' } - let(:target_branch) { 'master' } + it "returns all merge requests having the same source branch" do + merge_request = create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: pipeline.ref) - let!(:pipeline) do - create(:ci_pipeline, - source: :merge_request, - project: project, - ref: source_branch, - merge_request: merge_request) + expect(pipeline.all_merge_requests).to eq([merge_request]) end - let(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: source_branch, - target_project: project, - target_branch: target_branch) - end + it "doesn't return merge requests having a different source branch" do + create(:merge_request, source_project: pipeline_project, target_project: project, source_branch: 'feature', target_branch: 'master') - it 'returns an associated merge request' do - expect(pipeline.all_merge_requests).to eq([merge_request]) + expect(pipeline.all_merge_requests).to be_empty end - context 'when there is another merge request pipeline that targets a different branch' do - let(:target_branch_2) { 'merge-test' } + context 'when there is a merge request pipeline' do + let(:source_branch) { 'feature' } + let(:target_branch) { 'master' } - let!(:pipeline_2) do + let!(:pipeline) do create(:ci_pipeline, source: :merge_request, - project: project, + project: pipeline_project, ref: source_branch, - merge_request: merge_request_2) + merge_request: merge_request) end - let(:merge_request_2) do + let(:merge_request) do create(:merge_request, - source_project: project, + source_project: pipeline_project, source_branch: source_branch, target_project: project, - target_branch: target_branch_2) + target_branch: target_branch) + end + + it 'returns an associated merge request' do + expect(pipeline.all_merge_requests).to eq([merge_request]) end - it 'does not return an associated merge request' do - expect(pipeline.all_merge_requests).not_to include(merge_request_2) + context 'when there is another merge request pipeline that targets a different branch' do + let(:target_branch_2) { 'merge-test' } + + let!(:pipeline_2) do + create(:ci_pipeline, + source: :merge_request, + project: pipeline_project, + ref: source_branch, + merge_request: merge_request_2) + end + + let(:merge_request_2) do + create(:merge_request, + source_project: pipeline_project, + source_branch: source_branch, + target_project: project, + target_branch: target_branch_2) + end + + it 'does not return an associated merge request' do + expect(pipeline.all_merge_requests).not_to include(merge_request_2) + end end end end + + it_behaves_like 'a method that returns all merge requests for a given pipeline' do + let(:pipeline_project) { project } + end + + context 'for a fork' do + let(:fork) { fork_project(project) } + + it_behaves_like 'a method that returns all merge requests for a given pipeline' do + let(:pipeline_project) { fork } + end + end end describe '#stuck?' do diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 2250ef301aa..81708b0c2ed 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -27,65 +27,6 @@ describe Clusters::Applications::Prometheus do end end - describe '#ready' do - let(:project) { create(:project) } - let(:cluster) { create(:cluster, projects: [project]) } - - it 'returns true when installed' do - application = build(:clusters_applications_prometheus, :installed, cluster: cluster) - - expect(application).to be_ready - end - - it 'returns false when not_installable' do - application = build(:clusters_applications_prometheus, :not_installable, cluster: cluster) - - expect(application).not_to be_ready - end - - it 'returns false when installable' do - application = build(:clusters_applications_prometheus, :installable, cluster: cluster) - - expect(application).not_to be_ready - end - - it 'returns false when scheduled' do - application = build(:clusters_applications_prometheus, :scheduled, cluster: cluster) - - expect(application).not_to be_ready - end - - it 'returns false when installing' do - application = build(:clusters_applications_prometheus, :installing, cluster: cluster) - - expect(application).not_to be_ready - end - - it 'returns false when errored' do - application = build(:clusters_applications_prometheus, :errored, cluster: cluster) - - expect(application).not_to be_ready - end - - it 'returns true when updating' do - application = build(:clusters_applications_prometheus, :updating, cluster: cluster) - - expect(application).to be_ready - end - - it 'returns true when updated' do - application = build(:clusters_applications_prometheus, :updated, cluster: cluster) - - expect(application).to be_ready - end - - it 'returns true when errored' do - application = build(:clusters_applications_prometheus, :update_errored, cluster: cluster) - - expect(application).to be_ready - end - end - describe '#prometheus_client' do context 'cluster is nil' do it 'returns nil' do diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 5e5b25cbf8a..6972fc03415 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -22,7 +22,7 @@ describe Clusters::Applications::Runner do it 'should be initialized with 4 arguments' do expect(subject.name).to eq('runner') expect(subject.chart).to eq('runner/gitlab-runner') - expect(subject.version).to eq('0.1.45') + expect(subject.version).to eq('0.2.0') expect(subject).to be_rbac expect(subject.repository).to eq('https://charts.gitlab.io') expect(subject.files).to eq(gitlab_runner.files) @@ -40,7 +40,7 @@ describe Clusters::Applications::Runner do let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') } it 'should be initialized with the locked version' do - expect(subject.version).to eq('0.1.45') + expect(subject.version).to eq('0.2.0') end end end diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index 55d83bc3a6b..40cb4eef60a 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -97,14 +97,31 @@ describe ApplicationSetting, 'TokenAuthenticatable' do end describe PersonalAccessToken, 'TokenAuthenticatable' do - let(:personal_access_token_name) { 'test-pat-01' } + shared_examples 'changes personal access token' do + it 'sets new token' do + subject + + expect(personal_access_token.token).to eq(token_value) + expect(personal_access_token.token_digest).to eq(Gitlab::CryptoHelper.sha256(token_value)) + end + end + + shared_examples 'does not change personal access token' do + it 'sets new token' do + subject + + expect(personal_access_token.token).to be(nil) + expect(personal_access_token.token_digest).to eq(token_digest) + end + end + let(:token_value) { 'token' } + let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) } let(:user) { create(:user) } let(:personal_access_token) do - described_class.new(name: personal_access_token_name, + described_class.new(name: 'test-pat-01', user_id: user.id, scopes: [:api], - token: token, token_digest: token_digest) end @@ -115,239 +132,71 @@ describe PersonalAccessToken, 'TokenAuthenticatable' do describe '.find_by_token' do subject { PersonalAccessToken.find_by_token(token_value) } - before do + it 'finds the token' do personal_access_token.save - end - context 'token_digest already exists' do - let(:token) { nil } - let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) } - - it 'finds the token' do - expect(subject).not_to be_nil - expect(subject.name).to eql(personal_access_token_name) - end - end - - context 'token_digest does not exist' do - let(:token) { token_value } - let(:token_digest) { nil } - - it 'finds the token' do - expect(subject).not_to be_nil - expect(subject.name).to eql(personal_access_token_name) - end + expect(subject).to eq(personal_access_token) end end describe '#set_token' do let(:new_token_value) { 'new-token' } - subject { personal_access_token.set_token(new_token_value) } - - context 'token_digest already exists' do - let(:token) { nil } - let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) } - it 'overwrites token_digest' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(new_token_value) - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(new_token_value)) - end - end - - context 'token_digest does not exist but token does' do - let(:token) { token_value } - let(:token_digest) { nil } - - it 'creates new token_digest and clears token' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(new_token_value) - expect(personal_access_token.token_digest).to eql(Gitlab::CryptoHelper.sha256(new_token_value)) - end - end - - context 'token_digest does not exist, nor token' do - let(:token) { nil } - let(:token_digest) { nil } + subject { personal_access_token.set_token(new_token_value) } - it 'creates new token_digest' do - subject + it 'sets new token' do + subject - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(new_token_value) - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(new_token_value)) - end + expect(personal_access_token.token).to eq(new_token_value) + expect(personal_access_token.token_digest).to eq(Gitlab::CryptoHelper.sha256(new_token_value)) end end describe '#ensure_token' do subject { personal_access_token.ensure_token } - context 'token_digest already exists' do - let(:token) { nil } - let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) } - - it 'does not change token fields' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to be_nil - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value)) - end - end - - context 'token_digest does not exist but token does' do - let(:token) { token_value } + context 'token_digest does not exist' do let(:token_digest) { nil } - it 'does not change token fields' do - subject - - expect(personal_access_token.read_attribute('token')).to eql(token_value) - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to be_nil - end + it_behaves_like 'changes personal access token' end - context 'token_digest does not exist, nor token' do - let(:token) { nil } - let(:token_digest) { nil } - - it 'creates token_digest' do - subject + context 'token_digest already generated' do + let(:token_digest) { 's3cr3t' } - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value)) - end + it_behaves_like 'does not change personal access token' end end describe '#ensure_token!' do subject { personal_access_token.ensure_token! } - context 'token_digest already exists' do - let(:token) { nil } - let(:token_digest) { Gitlab::CryptoHelper.sha256(token_value) } - - it 'does not change token fields' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to be_nil - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value)) - end - end - - context 'token_digest does not exist but token does' do - let(:token) { token_value } + context 'token_digest does not exist' do let(:token_digest) { nil } - it 'does not change token fields' do - subject - - expect(personal_access_token.read_attribute('token')).to eql(token_value) - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to be_nil - end + it_behaves_like 'changes personal access token' end - context 'token_digest does not exist, nor token' do - let(:token) { nil } - let(:token_digest) { nil } + context 'token_digest already generated' do + let(:token_digest) { 's3cr3t' } - it 'creates token_digest' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value)) - end + it_behaves_like 'does not change personal access token' end end describe '#reset_token!' do subject { personal_access_token.reset_token! } - context 'token_digest already exists' do - let(:token) { nil } - let(:token_digest) { Gitlab::CryptoHelper.sha256('old-token') } - - it 'creates new token_digest' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value)) - end - end - - context 'token_digest does not exist but token does' do - let(:token) { 'old-token' } - let(:token_digest) { nil } - - it 'creates new token_digest and clears token' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to eql(Gitlab::CryptoHelper.sha256(token_value)) - end - end - - context 'token_digest does not exist, nor token' do - let(:token) { nil } + context 'token_digest does not exist' do let(:token_digest) { nil } - it 'creates new token_digest' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value)) - end - end - - context 'token_digest exists and newly generated token would be the same' do - let(:token) { nil } - let(:token_digest) { Gitlab::CryptoHelper.sha256('old-token') } - - before do - personal_access_token.save - allow(Devise).to receive(:friendly_token).and_return( - 'old-token', token_value, 'boom!') - end - - it 'regenerates a new token_digest' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value)) - end + it_behaves_like 'changes personal access token' end - context 'token exists and newly generated token would be the same' do - let(:token) { 'old-token' } - let(:token_digest) { nil } - - before do - personal_access_token.save - allow(Devise).to receive(:friendly_token).and_return( - 'old-token', token_value, 'boom!') - end + context 'token_digest already generated' do + let(:token_digest) { 's3cr3t' } - it 'regenerates a new token_digest' do - subject - - expect(personal_access_token.read_attribute('token')).to be_nil - expect(personal_access_token.token).to eql(token_value) - expect(personal_access_token.token_digest).to eql( Gitlab::CryptoHelper.sha256(token_value)) - end + it_behaves_like 'changes personal access token' end end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 2d554326f05..ab1b306e597 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -164,6 +164,28 @@ describe Environment do end end + describe '#name_without_type' do + context 'when it is inside a folder' do + subject(:environment) do + create(:environment, name: 'staging/review-1') + end + + it 'returns name without folder' do + expect(environment.name_without_type).to eq 'review-1' + end + end + + context 'when the environment if a top-level item itself' do + subject(:environment) do + create(:environment, name: 'production') + end + + it 'returns full name' do + expect(environment.name_without_type).to eq 'production' + end + end + end + describe '#nullify_external_url' do it 'replaces a blank url with nil' do env = build(:environment, external_url: "") diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index d30228b863c..076ccc96041 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -145,6 +145,24 @@ describe ErrorTracking::ProjectErrorTrackingSetting do expect(result).to be_nil end end + + context 'when sentry client raises exception' do + let(:sentry_client) { spy(:sentry_client) } + + before do + synchronous_reactive_cache(subject) + + allow(subject).to receive(:sentry_client).and_return(sentry_client) + allow(sentry_client).to receive(:list_issues).with(opts) + .and_raise(Sentry::Client::Error, 'error message') + end + + it 'returns error' do + expect(result).to eq(error: 'error message') + expect(subject).to have_received(:sentry_client) + expect(sentry_client).to have_received(:list_issues) + end + end end describe '#list_sentry_projects' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 5d18e085a6f..6101df2e099 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -765,6 +765,15 @@ describe Issue do end end + describe '.confidential_only' do + it 'only returns confidential_only issues' do + create(:issue) + confidential_issue = create(:issue, confidential: true) + + expect(described_class.confidential_only).to eq([confidential_issue]) + end + end + it_behaves_like 'throttled touch' do subject { create(:issue, updated_at: 1.hour.ago) } end diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb index 0d95f454819..5c4bce90ace 100644 --- a/spec/models/project_services/slack_slash_commands_service_spec.rb +++ b/spec/models/project_services/slack_slash_commands_service_spec.rb @@ -38,4 +38,11 @@ describe SlackSlashCommandsService do end end end + + describe '#chat_responder' do + it 'returns the responder to use for Slack' do + expect(described_class.new.chat_responder) + .to eq(Gitlab::Chat::Responder::Slack) + end + end end diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb new file mode 100644 index 00000000000..9524b526a46 --- /dev/null +++ b/spec/models/project_services/youtrack_service_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe YoutrackService do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + end + + context 'when service is inactive' do + before do + subject.active = false + end + + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + end + end + + describe '.reference_pattern' do + it_behaves_like 'allows project key on reference pattern' + + it 'does allow project prefix on the reference' do + expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YT-123') + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1f9088c2e6b..9cc9894003d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -50,6 +50,7 @@ describe Project do it { is_expected.to have_one(:teamcity_service) } it { is_expected.to have_one(:jira_service) } it { is_expected.to have_one(:redmine_service) } + it { is_expected.to have_one(:youtrack_service) } it { is_expected.to have_one(:custom_issue_tracker_service) } it { is_expected.to have_one(:bugzilla_service) } it { is_expected.to have_one(:gitlab_issue_tracker_service) } @@ -428,6 +429,30 @@ describe Project do end end + describe '#ci_pipelines' do + let(:project) { create(:project) } + + before do + create(:ci_pipeline, project: project, ref: 'master', source: :web) + create(:ci_pipeline, project: project, ref: 'master', source: :external) + end + + it 'has ci pipelines' do + expect(project.ci_pipelines.size).to eq(2) + end + + context 'when builds are disabled' do + before do + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) + end + + it 'should return .external pipelines' do + expect(project.ci_pipelines).to all(have_attributes(source: 'external')) + expect(project.ci_pipelines.size).to eq(1) + end + end + end + describe 'project token' do it 'sets an random token if none provided' do project = FactoryBot.create(:project, runners_token: '') @@ -458,6 +483,7 @@ describe Project do it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) } it { is_expected.to delegate_method(:group_clusters_enabled?).to(:group).with_arguments(allow_nil: true) } it { is_expected.to delegate_method(:root_ancestor).to(:namespace).with_arguments(allow_nil: true) } + it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) } end describe '#to_reference_with_postfix' do @@ -2486,6 +2512,16 @@ describe Project do end end + describe '#set_repository_writable!' do + it 'sets repository_read_only to false' do + project = create(:project, :read_only) + + expect { project.set_repository_writable! } + .to change(project, :repository_read_only) + .from(true).to(false) + end + end + describe '#pushes_since_gc' do let(:project) { create(:project) } diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb index 2b978c1c8ff..3610408c138 100644 --- a/spec/models/prometheus_metric_spec.rb +++ b/spec/models/prometheus_metric_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' describe PrometheusMetric do subject { build(:prometheus_metric) } - let(:other_project) { build(:project) } it_behaves_like 'having unique enum values' @@ -16,17 +15,17 @@ describe PrometheusMetric do describe 'common metrics' do using RSpec::Parameterized::TableSyntax - where(:common, :project, :result) do - false | other_project | true - false | nil | false - true | other_project | false - true | nil | true + where(:common, :with_project, :result) do + false | true | true + false | false | false + true | true | false + true | false | true end with_them do before do subject.common = common - subject.project = project + subject.project = with_project ? build(:project) : nil end it { expect(subject.valid?).to eq(result) } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f78760bf567..17201d8b90a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1373,6 +1373,29 @@ describe Repository do end end + describe '#merge_to_ref' do + let(:merge_request) do + create(:merge_request, source_branch: 'feature', + target_branch: 'master', + source_project: project) + end + + it 'writes merge of source and target to MR merge_ref_path' do + merge_commit_id = repository.merge_to_ref(user, + merge_request.diff_head_sha, + merge_request, + merge_request.merge_ref_path, + 'Custom message') + + merge_commit = repository.commit(merge_commit_id) + + expect(merge_commit.message).to eq('Custom message') + expect(merge_commit.author_name).to eq(user.name) + expect(merge_commit.author_email).to eq(user.commit_email) + expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present + end + end + describe '#ff_merge' do before do repository.add_branch(user, 'ff-target', 'feature~5') diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 1edd8e69b8f..85b157a9435 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -976,43 +976,43 @@ describe User do end end - describe '.filter' do + describe '.filter_items' do let(:user) { double } it 'filters by active users by default' do expect(described_class).to receive(:active).and_return([user]) - expect(described_class.filter(nil)).to include user + expect(described_class.filter_items(nil)).to include user end it 'filters by admins' do expect(described_class).to receive(:admins).and_return([user]) - expect(described_class.filter('admins')).to include user + expect(described_class.filter_items('admins')).to include user end it 'filters by blocked' do expect(described_class).to receive(:blocked).and_return([user]) - expect(described_class.filter('blocked')).to include user + expect(described_class.filter_items('blocked')).to include user end it 'filters by two_factor_disabled' do expect(described_class).to receive(:without_two_factor).and_return([user]) - expect(described_class.filter('two_factor_disabled')).to include user + expect(described_class.filter_items('two_factor_disabled')).to include user end it 'filters by two_factor_enabled' do expect(described_class).to receive(:with_two_factor).and_return([user]) - expect(described_class.filter('two_factor_enabled')).to include user + expect(described_class.filter_items('two_factor_enabled')).to include user end it 'filters by wop' do expect(described_class).to receive(:without_projects).and_return([user]) - expect(described_class.filter('wop')).to include user + expect(described_class.filter_items('wop')).to include user end end diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index 170e0ac5717..f50bcf54b46 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -98,4 +98,72 @@ describe Ci::BuildRunnerPresenter do end end end + + describe '#ref_type' do + subject { presenter.ref_type } + + let(:build) { create(:ci_build, tag: tag) } + let(:tag) { true } + + it 'returns the correct ref type' do + is_expected.to eq('tag') + end + + context 'when tag is false' do + let(:tag) { false } + + it 'returns the correct ref type' do + is_expected.to eq('branch') + end + end + end + + describe '#git_depth' do + subject { presenter.git_depth } + + let(:build) { create(:ci_build) } + + it 'returns the correct git depth' do + is_expected.to eq(0) + end + + context 'when GIT_DEPTH variable is specified' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline) + end + + it 'returns the correct git depth' do + is_expected.to eq(1) + end + end + end + + describe '#refspecs' do + subject { presenter.refspecs } + + let(:build) { create(:ci_build) } + + it 'returns the correct refspecs' do + is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', + '+refs/heads/*:refs/remotes/origin/*') + end + + context 'when GIT_DEPTH variable is specified' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: build.pipeline) + end + + it 'returns the correct refspecs' do + is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}") + end + + context 'when ref is tag' do + let(:build) { create(:ci_build, :tag) } + + it 'returns the correct refspecs' do + is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}") + end + end + end + end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index d10ee6cc320..01bab2a1361 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -183,6 +183,18 @@ describe API::Issues do expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end + it 'returns only confidential issues' do + get api('/issues', user), params: { confidential: true, scope: 'all' } + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns only public issues' do + get api('/issues', user), params: { confidential: false } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + it 'returns issues reacted by the authenticated user' do issue2 = create(:issue, project: project, author: user, assignees: [user]) create(:award_emoji, awardable: issue2, user: user2, name: 'star') @@ -354,7 +366,7 @@ describe API::Issues do end it 'returns an empty array if iid does not exist' do - get api("/issues", user), params: { iids: [99999] } + get api("/issues", user), params: { iids: [0] } expect_paginated_array_response([]) end @@ -557,6 +569,18 @@ describe API::Issues do expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) end + it 'returns only confidential issues' do + get api(base_url, user), params: { confidential: true } + + expect_paginated_array_response(group_confidential_issue.id) + end + + it 'returns only public issues' do + get api(base_url, user), params: { confidential: false } + + expect_paginated_array_response([group_closed_issue.id, group_issue.id]) + end + it 'returns an array of labeled group issues' do get api(base_url, user), params: { labels: group_label.title } @@ -603,7 +627,7 @@ describe API::Issues do end it 'returns an empty array if iid does not exist' do - get api(base_url, user), params: { iids: [99999] } + get api(base_url, user), params: { iids: [0] } expect_paginated_array_response([]) end @@ -782,6 +806,18 @@ describe API::Issues do expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) end + it 'returns only confidential issues' do + get api("#{base_url}/issues", author), params: { confidential: true } + + expect_paginated_array_response(confidential_issue.id) + end + + it 'returns only public issues' do + get api("#{base_url}/issues", author), params: { confidential: false } + + expect_paginated_array_response([issue.id, closed_issue.id]) + end + it 'returns project confidential issues for assignee' do get api("#{base_url}/issues", assignee) @@ -837,7 +873,7 @@ describe API::Issues do end it 'returns an empty array if iid does not exist' do - get api("#{base_url}/issues", user), params: { iids: [99999] } + get api("#{base_url}/issues", user), params: { iids: [0] } expect_paginated_array_response([]) end @@ -1873,7 +1909,7 @@ describe API::Issues do end it "returns 404 when issue doesn't exists" do - get api("/projects/#{project.id}/issues/9999/closed_by", user) + get api("/projects/#{project.id}/issues/0/closed_by", user) expect(response).to have_gitlab_http_status(404) end @@ -1958,7 +1994,7 @@ describe API::Issues do end it "returns 404 when issue doesn't exists" do - get_related_merge_requests(project.id, 999999, user) + get_related_merge_requests(project.id, 0, user) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index 3c4719964b6..f37d84fddef 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -16,7 +16,7 @@ describe API::Keys do context 'when authenticated' do it 'returns 404 for non-existing key' do - get api('/keys/999999', admin) + get api('/keys/0', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 Not found') end diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb index 6530dc956cb..8a67d98fc4c 100644 --- a/spec/requests/api/merge_request_diffs_spec.rb +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -30,7 +30,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do end it 'returns a 404 when merge_request_iid not found' do - get api("/projects/#{project.id}/merge_requests/999/versions", user) + get api("/projects/#{project.id}/merge_requests/0/versions", user) expect(response).to have_gitlab_http_status(404) end end @@ -53,7 +53,7 @@ describe API::MergeRequestDiffs, 'MergeRequestDiffs' do end it 'returns a 404 when merge_request version_id is not found' do - get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/999", user) + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/versions/0", user) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index b8426126bc6..8aba0bc8e18 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -320,6 +320,18 @@ describe API::MergeRequests do expect(json_response.first['title']).to eq merge_request_closed.title expect(json_response.first['id']).to eq merge_request_closed.id end + + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new do + get api("/projects/#{project.id}/merge_requests", user) + end.count + + create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, created_at: base_time) + + expect do + get api("/projects/#{project.id}/merge_requests", user) + end.not_to exceed_query_limit(control) + end end describe "GET /groups/:id/merge_requests" do @@ -441,7 +453,7 @@ describe API::MergeRequests do end it "returns a 404 error if merge_request_iid not found" do - get api("/projects/#{project.id}/merge_requests/999", user) + get api("/projects/#{project.id}/merge_requests/0", user) expect(response).to have_gitlab_http_status(404) end @@ -531,7 +543,7 @@ describe API::MergeRequests do end it 'returns a 404 when merge_request_iid not found' do - get api("/projects/#{project.id}/merge_requests/999/commits", user) + get api("/projects/#{project.id}/merge_requests/0/commits", user) expect(response).to have_gitlab_http_status(404) end @@ -551,7 +563,7 @@ describe API::MergeRequests do end it 'returns a 404 when merge_request_iid not found' do - get api("/projects/#{project.id}/merge_requests/999/changes", user) + get api("/projects/#{project.id}/merge_requests/0/changes", user) expect(response).to have_gitlab_http_status(404) end @@ -984,6 +996,21 @@ describe API::MergeRequests do expect(squash_commit.message).to eq(merge_request.default_squash_commit_message) end end + + describe "the should_remove_source_branch param" do + let(:source_repository) { merge_request.source_project.repository } + let(:source_branch) { merge_request.source_branch } + + it 'removes the source branch when set' do + put( + api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), + params: { should_remove_source_branch: true } + ) + + expect(response).to have_gitlab_http_status(200) + expect(source_repository.branch_exists?(source_branch)).to be_falsy + end + end end describe "PUT /projects/:id/merge_requests/:merge_request_iid" do diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 145356c4df5..2e376109b42 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -149,7 +149,7 @@ describe API::Namespaces do context "when namespace doesn't exist" do it 'returns not-found' do - get api('/namespaces/9999', request_actor) + get api('/namespaces/0', request_actor) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 49b5dfb0b33..895f05a98e8 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -23,13 +23,13 @@ describe API::ProjectMilestones do end it 'returns 404 response when the project does not exists' do - delete api("/projects/999/milestones/#{milestone.id}", user) + delete api("/projects/0/milestones/#{milestone.id}", user) expect(response).to have_gitlab_http_status(404) end it 'returns 404 response when the milestone does not exists' do - delete api("/projects/#{project.id}/milestones/999", user) + delete api("/projects/#{project.id}/milestones/0", user) expect(response).to have_gitlab_http_status(404) end @@ -49,4 +49,74 @@ describe API::ProjectMilestones do params: { state_event: 'close' } end end + + describe 'POST /projects/:id/milestones/:milestone_id/promote' do + let(:group) { create(:group) } + + before do + project.update(namespace: group) + end + + context 'when user does not have permission to promote milestone' do + before do + group.add_guest(user) + end + + it 'returns 403' do + post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(403) + end + end + + context 'when user has permission' do + before do + group.add_developer(user) + end + + it 'returns 200' do + post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(200) + expect(group.milestones.first.title).to eq(milestone.title) + end + + it 'returns 200 for closed milestone' do + post api("/projects/#{project.id}/milestones/#{closed_milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(200) + expect(group.milestones.first.title).to eq(closed_milestone.title) + end + end + + context 'when no such resources' do + before do + group.add_developer(user) + end + + it 'returns 404 response when the project does not exist' do + post api("/projects/0/milestones/#{milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 response when the milestone does not exist' do + post api("/projects/#{project.id}/milestones/0/promote", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when project does not belong to group' do + before do + project.update(namespace: user.namespace) + end + + it 'returns 403' do + post api("/projects/#{project.id}/milestones/#{milestone.id}/promote", user) + + expect(response).to have_gitlab_http_status(403) + end + end + end end diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb index ab5d4de7ff7..80e5033dab4 100644 --- a/spec/requests/api/project_templates_spec.rb +++ b/spec/requests/api/project_templates_spec.rb @@ -92,6 +92,22 @@ describe API::ProjectTemplates do expect(json_response['name']).to eq('Actionscript') end + it 'returns C++ gitignore' do + get api("/projects/#{public_project.id}/templates/gitignores/C++") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('C++') + end + + it 'returns C++ gitignore for URL-encoded names' do + get api("/projects/#{public_project.id}/templates/gitignores/C%2B%2B") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('C++') + end + it 'returns a specific gitlab_ci_yml' do get api("/projects/#{public_project.id}/templates/gitlab_ci_ymls/Android") @@ -125,6 +141,18 @@ describe API::ProjectTemplates do expect(response).to have_gitlab_http_status(200) expect(response).to match_response_schema('public_api/v4/license') end + + shared_examples 'path traversal attempt' do |template_type| + it 'rejects invalid filenames' do + get api("/projects/#{public_project.id}/templates/#{template_type}/%2e%2e%2fPython%2ea") + + expect(response).to have_gitlab_http_status(500) + end + end + + TemplateFinder::VENDORED_TEMPLATES.each do |template_type, _| + it_behaves_like 'path traversal attempt', template_type + end end describe 'GET /projects/:id/templates/licenses/:key' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cfa7a1a31a3..856fe1bbe89 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -4,6 +4,15 @@ require 'spec_helper' shared_examples 'languages and percentages JSON response' do let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h } + before do + allow(project.repository).to receive(:languages).and_return( + [{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" }, + { value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" }, + { value: 7.91, label: "HTML", color: "#e34c26", highlight: "#e34c26" }, + { value: 2.42, label: "CoffeeScript", color: "#244776", highlight: "#244776" }] + ) + end + it 'returns expected language values' do get api("/projects/#{project.id}/languages", user) @@ -11,6 +20,23 @@ shared_examples 'languages and percentages JSON response' do expect(json_response).to eq(expected_languages) expect(json_response.count).to be > 1 end + + context 'when the languages were detected before' do + before do + Projects::DetectRepositoryLanguagesService.new(project, project.owner).execute + end + + it 'returns the detection from the database' do + # Allow this to happen once, so the expected languages can be determined + expect(project.repository).to receive(:languages).once + + get api("/projects/#{project.id}/languages", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq(expected_languages) + expect(json_response.count).to be > 1 + end + end end describe API::Projects do @@ -752,7 +778,7 @@ describe API::Projects do let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) } it 'returns error when user not found' do - get api('/users/9999/projects/') + get api('/users/0/projects/') expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1359,7 +1385,7 @@ describe API::Projects do end it 'fails if forked_from project which does not exist' do - post api("/projects/#{project_fork_target.id}/fork/9999", admin) + post api("/projects/#{project_fork_target.id}/fork/0", admin) expect(response).to have_gitlab_http_status(404) end @@ -1910,7 +1936,7 @@ describe API::Projects do end it 'returns not_found(404) for not existing project' do - get api("/projects/9999999999/languages", user) + get api("/projects/0/languages", user) expect(response).to have_gitlab_http_status(:not_found) end @@ -1995,6 +2021,11 @@ describe API::Projects do let(:project) do create(:project, :repository, creator: user, namespace: user.namespace) end + + let(:project2) do + create(:project, :repository, creator: user, namespace: user.namespace) + end + let(:group) { create(:group) } let(:group2) do group = create(:group, name: 'group2_name') @@ -2010,6 +2041,7 @@ describe API::Projects do before do project.add_reporter(user2) + project2.add_reporter(user2) end context 'when authenticated' do @@ -2124,6 +2156,48 @@ describe API::Projects do expect(response).to have_gitlab_http_status(201) expect(json_response['namespace']['name']).to eq(group.name) end + + it 'accepts a path for the target project' do + post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to eq('foobar') + expect(json_response['owner']['id']).to eq(user2.id) + expect(json_response['namespace']['id']).to eq(user2.namespace.id) + expect(json_response['forked_from_project']['id']).to eq(project.id) + expect(json_response['import_status']).to eq('scheduled') + expect(json_response).to include("import_error") + end + + it 'fails to fork if path is already taken' do + post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' } + post api("/projects/#{project2.id}/fork", user2), params: { path: 'foobar' } + + expect(response).to have_gitlab_http_status(409) + expect(json_response['message']['path']).to eq(['has already been taken']) + end + + it 'accepts a name for the target project' do + post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq('My Random Project') + expect(json_response['path']).to eq(project.path) + expect(json_response['owner']['id']).to eq(user2.id) + expect(json_response['namespace']['id']).to eq(user2.namespace.id) + expect(json_response['forked_from_project']['id']).to eq(project.id) + expect(json_response['import_status']).to eq('scheduled') + expect(json_response).to include("import_error") + end + + it 'fails to fork if name is already taken' do + post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' } + post api("/projects/#{project2.id}/fork", user2), params: { name: 'My Random Project' } + + expect(response).to have_gitlab_http_status(409) + expect(json_response['message']['name']).to eq(['has already been taken']) + end end context 'when unauthenticated' do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index d7ddd97e8c8..e6c235ca26e 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -417,7 +417,9 @@ describe API::Runner, :clean_gitlab_redis_shared_state do 'ref' => job.ref, 'sha' => job.sha, 'before_sha' => job.before_sha, - 'ref_type' => 'branch' } + 'ref_type' => 'branch', + 'refspecs' => %w[+refs/heads/*:refs/remotes/origin/* +refs/tags/*:refs/tags/*], + 'depth' => 0 } end let(:expected_steps) do @@ -489,6 +491,29 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(201) expect(json_response['git_info']['ref_type']).to eq('tag') end + + context 'when GIT_DEPTH is specified' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline) + end + + it 'specifies refspecs' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['git_info']['refspecs']).to include("+refs/tags/#{job.ref}:refs/tags/#{job.ref}") + end + end + + context 'when GIT_DEPTH is not specified' do + it 'specifies refspecs' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['git_info']['refspecs']) + .to contain_exactly('+refs/tags/*:refs/tags/*', '+refs/heads/*:refs/remotes/origin/*') + end + end end context 'when job is made for branch' do @@ -498,6 +523,55 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(201) expect(json_response['git_info']['ref_type']).to eq('branch') end + + context 'when GIT_DEPTH is specified' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline) + end + + it 'specifies refspecs' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['git_info']['refspecs']).to include("+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}") + end + end + + context 'when GIT_DEPTH is not specified' do + it 'specifies refspecs' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['git_info']['refspecs']) + .to contain_exactly('+refs/tags/*:refs/tags/*', '+refs/heads/*:refs/remotes/origin/*') + end + end + end + + context 'when job is made for merge request' do + let(:pipeline) { create(:ci_pipeline_without_jobs, source: :merge_request, project: project, ref: 'feature', merge_request: merge_request) } + let!(:job) { create(:ci_build, pipeline: pipeline, name: 'spinach', ref: 'feature', stage: 'test', stage_idx: 0) } + let(:merge_request) { create(:merge_request) } + + it 'sets branch as ref_type' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['git_info']['ref_type']).to eq('branch') + end + + context 'when GIT_DEPTH is specified' do + before do + create(:ci_pipeline_variable, key: 'GIT_DEPTH', value: 1, pipeline: pipeline) + end + + it 'returns the overwritten git depth for merge request refspecs' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['git_info']['depth']).to eq(1) + end + end end it 'updates runner info' do @@ -526,6 +600,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(runner.reload.ip_address).to eq('123.222.123.222') end + it "handles multiple X-Forwarded-For addresses" do + post api('/jobs/request'), + params: { token: runner.token }, + headers: { 'User-Agent' => user_agent, 'X-Forwarded-For' => '123.222.123.222, 127.0.0.1' } + + expect(response).to have_gitlab_http_status 201 + expect(runner.reload.ip_address).to eq('123.222.123.222') + end + context 'when concurrently updating a job' do before do expect_any_instance_of(Ci::Build).to receive(:run!) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 7f11c8c9fe8..5ca442bc448 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -241,7 +241,7 @@ describe API::Runners do end it 'returns 404 if runner does not exists' do - get api('/runners/9999', admin) + get api('/runners/0', admin) expect(response).to have_gitlab_http_status(404) end @@ -394,7 +394,7 @@ describe API::Runners do end it 'returns 404 if runner does not exists' do - update_runner(9999, admin, description: 'test') + update_runner(0, admin, description: 'test') expect(response).to have_gitlab_http_status(404) end @@ -468,7 +468,7 @@ describe API::Runners do end it 'returns 404 if runner does not exists' do - delete api('/runners/9999', admin) + delete api('/runners/0', admin) expect(response).to have_gitlab_http_status(404) end @@ -573,7 +573,7 @@ describe API::Runners do context "when runner doesn't exist" do it 'returns 404' do - get api('/runners/9999/jobs', admin) + get api('/runners/0/jobs', admin) expect(response).to have_gitlab_http_status(404) end @@ -626,7 +626,7 @@ describe API::Runners do context "when runner doesn't exist" do it 'returns 404' do - get api('/runners/9999/jobs', user) + get api('/runners/0/jobs', user) expect(response).to have_gitlab_http_status(404) end @@ -857,7 +857,7 @@ describe API::Runners do end it 'returns 404 is runner is not found' do - delete api("/projects/#{project.id}/runners/9999", user) + delete api("/projects/#{project.id}/runners/0", user) expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb index 831f47debeb..c48ca832c85 100644 --- a/spec/requests/api/search_spec.rb +++ b/spec/requests/api/search_spec.rb @@ -126,7 +126,7 @@ describe API::Search do context 'when group does not exist' do it 'returns 404 error' do - get api('/groups/9999/search', user), params: { scope: 'issues', search: 'awesome' } + get api('/groups/0/search', user), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(404) end @@ -222,7 +222,7 @@ describe API::Search do context 'when project does not exist' do it 'returns 404 error' do - get api('/projects/9999/search', user), params: { scope: 'issues', search: 'awesome' } + get api('/projects/0/search', user), params: { scope: 'issues', search: 'awesome' } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index b381431306d..a879426589d 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -335,7 +335,7 @@ describe API::Users do end it "returns a 404 error if user id not found" do - get api("/users/9999", user) + get api("/users/0", user) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -732,7 +732,7 @@ describe API::Users do end it "returns 404 for non-existing user" do - put api("/users/999999", admin), params: { bio: 'update should fail' } + put api("/users/0", admin), params: { bio: 'update should fail' } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -836,7 +836,7 @@ describe API::Users do end it "returns 400 for invalid ID" do - post api("/users/999999/keys", admin) + post api("/users/0/keys", admin) expect(response).to have_gitlab_http_status(400) end end @@ -895,7 +895,7 @@ describe API::Users do it 'returns 404 error if user not found' do user.keys << key user.save - delete api("/users/999999/keys/#{key.id}", admin) + delete api("/users/0/keys/#{key.id}", admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -930,7 +930,7 @@ describe API::Users do end it 'returns 400 for invalid ID' do - post api('/users/999999/gpg_keys', admin) + post api('/users/0/gpg_keys', admin) expect(response).to have_gitlab_http_status(400) end @@ -951,7 +951,7 @@ describe API::Users do context 'when authenticated' do it 'returns 404 for non-existing user' do - get api('/users/999999/gpg_keys', admin) + get api('/users/0/gpg_keys', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1007,7 +1007,7 @@ describe API::Users do user.keys << key user.save - delete api("/users/999999/gpg_keys/#{gpg_key.id}", admin) + delete api("/users/0/gpg_keys/#{gpg_key.id}", admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1051,7 +1051,7 @@ describe API::Users do user.gpg_keys << gpg_key user.save - post api("/users/999999/gpg_keys/#{gpg_key.id}/revoke", admin) + post api("/users/0/gpg_keys/#{gpg_key.id}/revoke", admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') @@ -1089,7 +1089,7 @@ describe API::Users do end it "returns a 400 for invalid ID" do - post api("/users/999999/emails", admin) + post api("/users/0/emails", admin) expect(response).to have_gitlab_http_status(400) end @@ -1121,7 +1121,7 @@ describe API::Users do context 'when authenticated' do it 'returns 404 for non-existing user' do - get api('/users/999999/emails', admin) + get api('/users/0/emails', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1177,7 +1177,7 @@ describe API::Users do it 'returns 404 error if user not found' do user.emails << email user.save - delete api("/users/999999/emails/#{email.id}", admin) + delete api("/users/0/emails/#{email.id}", admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1227,7 +1227,7 @@ describe API::Users do end it "returns 404 for non-existing user" do - perform_enqueued_jobs { delete api("/users/999999", admin) } + perform_enqueued_jobs { delete api("/users/0", admin) } expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1778,7 +1778,7 @@ describe API::Users do end it 'returns a 404 error if user id not found' do - post api('/users/9999/block', admin) + post api('/users/0/block', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end @@ -1816,7 +1816,7 @@ describe API::Users do end it 'returns a 404 error if user id not found' do - post api('/users/9999/block', admin) + post api('/users/0/block', admin) expect(response).to have_gitlab_http_status(404) expect(json_response['message']).to eq('404 User Not Found') end diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb index 3541bd5f12e..375a28a8c72 100644 --- a/spec/serializers/environment_serializer_spec.rb +++ b/spec/serializers/environment_serializer_spec.rb @@ -124,10 +124,10 @@ describe EnvironmentSerializer do end context 'when used with pagination' do - let(:request) { spy('request') } + let(:request) { double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query) } let(:response) { spy('response') } let(:resource) { Environment.all } - let(:pagination) { { page: 1, per_page: 2 } } + let(:query) { { page: 1, per_page: 2 } } let(:serializer) do described_class @@ -135,11 +135,6 @@ describe EnvironmentSerializer do .with_pagination(request, response) end - before do - allow(request).to receive(:query_parameters) - .and_return(pagination) - end - subject { serializer.represent(resource) } it 'creates a paginated serializer' do diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 79aa32b29bb..2bdcb2a45f6 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -38,15 +38,9 @@ describe PipelineSerializer do end context 'when used with pagination' do - let(:request) { spy('request') } + let(:request) { double(url: "#{Gitlab.config.gitlab.url}:8080/api/v4/projects?#{query.to_query}", query_parameters: query) } let(:response) { spy('response') } - let(:pagination) { {} } - - before do - allow(request) - .to receive(:query_parameters) - .and_return(pagination) - end + let(:query) { {} } let(:serializer) do described_class.new(current_user: user) @@ -60,7 +54,7 @@ describe PipelineSerializer do context 'when resource is not paginatable' do context 'when a single pipeline object is being serialized' do let(:resource) { create(:ci_empty_pipeline) } - let(:pagination) { { page: 1, per_page: 1 } } + let(:query) { { page: 1, per_page: 1 } } it 'raises error' do expect { subject }.to raise_error( @@ -71,7 +65,7 @@ describe PipelineSerializer do context 'when resource is paginatable relation' do let(:resource) { Ci::Pipeline.all } - let(:pagination) { { page: 1, per_page: 2 } } + let(:query) { { page: 1, per_page: 2 } } context 'when a single pipeline object is present in relation' do before do diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 8497e90bd8b..93349ba7b5b 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -12,6 +12,7 @@ describe Ci::CreatePipelineService do end describe '#execute' do + # rubocop:disable Metrics/ParameterLists def execute_service( source: :push, after: project.commit.id, @@ -20,17 +21,22 @@ describe Ci::CreatePipelineService do trigger_request: nil, variables_attributes: nil, merge_request: nil, - push_options: nil) + push_options: nil, + source_sha: nil, + target_sha: nil) params = { ref: ref, before: '00000000', after: after, commits: [{ message: message }], variables_attributes: variables_attributes, - push_options: push_options } + push_options: push_options, + source_sha: source_sha, + target_sha: target_sha } described_class.new(project, user, params).execute( source, trigger_request: trigger_request, merge_request: merge_request) end + # rubocop:enable Metrics/ParameterLists context 'valid params' do let(:pipeline) { execute_service } @@ -679,7 +685,11 @@ describe Ci::CreatePipelineService do describe 'Merge request pipelines' do let(:pipeline) do - execute_service(source: source, merge_request: merge_request, ref: ref_name) + execute_service(source: source, + merge_request: merge_request, + ref: ref_name, + source_sha: source_sha, + target_sha: target_sha) end before do @@ -687,6 +697,8 @@ describe Ci::CreatePipelineService do end let(:ref_name) { 'refs/heads/feature' } + let(:source_sha) { project.commit(ref_name).id } + let(:target_sha) { nil } context 'when source is merge request' do let(:source) { :merge_request } @@ -727,6 +739,22 @@ describe Ci::CreatePipelineService do expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[test]) end + it 'persists the specified source sha' do + expect(pipeline.source_sha).to eq(source_sha) + end + + it 'does not persist target sha for detached merge request pipeline' do + expect(pipeline.target_sha).to be_nil + end + + context 'when target sha is specified' do + let(:target_sha) { merge_request.target_branch_sha } + + it 'persists the target sha' do + expect(pipeline.target_sha).to eq(target_sha) + end + end + context 'when ref is tag' do let(:ref_name) { 'refs/tags/v1.1.0' } diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb index 3f621ed5944..cbdef008b07 100644 --- a/spec/services/clusters/applications/create_service_spec.rb +++ b/spec/services/clusters/applications/create_service_spec.rb @@ -26,12 +26,6 @@ describe Clusters::Applications::CreateService do end.to change(cluster, :application_helm) end - it 'schedules an install via worker' do - expect(ClusterInstallAppWorker).to receive(:perform_async).with('helm', anything).once - - subject - end - context 'application already installed' do let!(:application) { create(:clusters_applications_helm, :installed, cluster: cluster) } @@ -42,88 +36,101 @@ describe Clusters::Applications::CreateService do end it 'schedules an upgrade for the application' do - expect(Clusters::Applications::ScheduleInstallationService).to receive(:new).with(application).and_call_original + expect(ClusterUpgradeAppWorker).to receive(:perform_async) subject end end - context 'cert manager application' do - let(:params) do - { - application: 'cert_manager', - email: 'test@example.com' - } - end - + context 'known applications' do before do - allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) + create(:clusters_applications_helm, :installed, cluster: cluster) end - it 'creates the application' do - expect do - subject + context 'cert manager application' do + let(:params) do + { + application: 'cert_manager', + email: 'test@example.com' + } + end - cluster.reload - end.to change(cluster, :application_cert_manager) - end + before do + expect_any_instance_of(Clusters::Applications::CertManager) + .to receive(:make_scheduled!) + .and_call_original + end - it 'sets the email' do - expect(subject.email).to eq('test@example.com') - end - end + it 'creates the application' do + expect do + subject - context 'jupyter application' do - let(:params) do - { - application: 'jupyter', - hostname: 'example.com' - } - end + cluster.reload + end.to change(cluster, :application_cert_manager) + end - before do - allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) + it 'sets the email' do + expect(subject.email).to eq('test@example.com') + end end - it 'creates the application' do - expect do - subject + context 'jupyter application' do + let(:params) do + { + application: 'jupyter', + hostname: 'example.com' + } + end - cluster.reload - end.to change(cluster, :application_jupyter) - end + before do + create(:clusters_applications_ingress, :installed, external_ip: "127.0.0.0", cluster: cluster) + expect_any_instance_of(Clusters::Applications::Jupyter) + .to receive(:make_scheduled!) + .and_call_original + end - it 'sets the hostname' do - expect(subject.hostname).to eq('example.com') - end + it 'creates the application' do + expect do + subject - it 'sets the oauth_application' do - expect(subject.oauth_application).to be_present - end - end + cluster.reload + end.to change(cluster, :application_jupyter) + end - context 'knative application' do - let(:params) do - { - application: 'knative', - hostname: 'example.com' - } - end + it 'sets the hostname' do + expect(subject.hostname).to eq('example.com') + end - before do - allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) + it 'sets the oauth_application' do + expect(subject.oauth_application).to be_present + end end - it 'creates the application' do - expect do - subject + context 'knative application' do + let(:params) do + { + application: 'knative', + hostname: 'example.com' + } + end - cluster.reload - end.to change(cluster, :application_knative) - end + before do + expect_any_instance_of(Clusters::Applications::Knative) + .to receive(:make_scheduled!) + .and_call_original + end - it 'sets the hostname' do - expect(subject.hostname).to eq('example.com') + it 'creates the application' do + expect do + subject + + cluster.reload + end.to change(cluster, :application_knative) + end + + it 'sets the hostname' do + expect(subject.hostname).to eq('example.com') + end end end @@ -140,19 +147,21 @@ describe Clusters::Applications::CreateService do using RSpec::Parameterized::TableSyntax - before do - allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) - end - - where(:application, :association, :allowed) do - 'helm' | :application_helm | true - 'ingress' | :application_ingress | true - 'runner' | :application_runner | false - 'jupyter' | :application_jupyter | false - 'prometheus' | :application_prometheus | false + where(:application, :association, :allowed, :pre_create_helm) do + 'helm' | :application_helm | true | false + 'ingress' | :application_ingress | true | true + 'runner' | :application_runner | false | true + 'jupyter' | :application_jupyter | false | true + 'prometheus' | :application_prometheus | false | true end with_them do + before do + klass = "Clusters::Applications::#{application.titleize}" + allow_any_instance_of(klass.constantize).to receive(:make_scheduled!).and_call_original + create(:clusters_applications_helm, :installed, cluster: cluster) if pre_create_helm + end + let(:params) { { application: application } } it 'executes for each application' do @@ -168,5 +177,68 @@ describe Clusters::Applications::CreateService do end end end + + context 'when application is installable' do + shared_examples 'installable applications' do + it 'makes the application scheduled' do + expect do + subject + end.to change { Clusters::Applications::Helm.with_status(:scheduled).count }.by(1) + end + + it 'schedules an install via worker' do + expect(ClusterInstallAppWorker) + .to receive(:perform_async) + .with(*worker_arguments) + .once + + subject + end + end + + context 'when application is associated with a cluster' do + let(:application) { create(:clusters_applications_helm, :installable, cluster: cluster) } + let(:worker_arguments) { [application.name, application.id] } + + it_behaves_like 'installable applications' + end + + context 'when application is not associated with a cluster' do + let(:worker_arguments) { [params[:application], kind_of(Numeric)] } + + it_behaves_like 'installable applications' + end + end + + context 'when installation is already in progress' do + let!(:application) { create(:clusters_applications_helm, :installing, cluster: cluster) } + + it 'raises an exception' do + expect { subject } + .to raise_exception(StateMachines::InvalidTransition) + .and not_change(application.class.with_status(:scheduled), :count) + end + + it 'does not schedule a cluster worker' do + expect(ClusterInstallAppWorker).not_to receive(:perform_async) + end + end + + context 'when application is installed' do + %i(installed updated).each do |status| + let(:application) { create(:clusters_applications_helm, status, cluster: cluster) } + + it 'schedules an upgrade via worker' do + expect(ClusterUpgradeAppWorker) + .to receive(:perform_async) + .with(application.name, application.id) + .once + + subject + + expect(application.reload).to be_scheduled + end + end + end end end diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb deleted file mode 100644 index 8380932dfaa..00000000000 --- a/spec/services/clusters/applications/schedule_installation_service_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'spec_helper' - -describe Clusters::Applications::ScheduleInstallationService do - def count_scheduled - application&.class&.with_status(:scheduled)&.count || 0 - end - - shared_examples 'a failing service' do - it 'raise an exception' do - expect(ClusterInstallAppWorker).not_to receive(:perform_async) - count_before = count_scheduled - - expect { service.execute }.to raise_error(StandardError) - expect(count_scheduled).to eq(count_before) - end - end - - describe '#execute' do - let(:service) { described_class.new(application) } - - context 'when application is installable' do - let(:application) { create(:clusters_applications_helm, :installable) } - - it 'make the application scheduled' do - expect(ClusterInstallAppWorker).to receive(:perform_async).with(application.name, kind_of(Numeric)).once - - expect { service.execute }.to change { application.class.with_status(:scheduled).count }.by(1) - end - end - - context 'when installation is already in progress' do - let(:application) { create(:clusters_applications_helm, :installing) } - - it_behaves_like 'a failing service' - end - - context 'when application is nil' do - let(:application) { nil } - - it_behaves_like 'a failing service' - end - - context 'when application cannot be persisted' do - let(:application) { create(:clusters_applications_helm) } - - before do - expect(application).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid) - end - - it_behaves_like 'a failing service' - end - - context 'when application is installed' do - let(:application) { create(:clusters_applications_helm, :installed) } - - it 'schedules an upgrade via worker' do - expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once - - service.execute - - expect(application).to be_scheduled - end - end - - context 'when application is updated' do - let(:application) { create(:clusters_applications_helm, :updated) } - - it 'schedules an upgrade via worker' do - expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once - - service.execute - - expect(application).to be_scheduled - end - end - end -end diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb index d9dab1d705c..9d4fc62f923 100644 --- a/spec/services/error_tracking/list_issues_service_spec.rb +++ b/spec/services/error_tracking/list_issues_service_spec.rb @@ -45,7 +45,23 @@ describe ErrorTracking::ListIssuesService do it 'result is not ready' do expect(result).to eq( - status: :error, http_status: :no_content, message: 'not ready') + status: :error, http_status: :no_content, message: 'Not ready. Try again later') + end + end + + context 'when list_sentry_issues returns error' do + before do + allow(error_tracking_setting) + .to receive(:list_sentry_issues) + .and_return(error: 'Sentry response status code: 401') + end + + it 'returns the error' do + expect(result).to eq( + status: :error, + http_status: :bad_request, + message: 'Sentry response status code: 401' + ) end end end @@ -58,7 +74,11 @@ describe ErrorTracking::ListIssuesService do it 'returns error' do result = subject.execute - expect(result).to include(status: :error, message: 'access denied') + expect(result).to include( + status: :error, + message: 'Access denied', + http_status: :unauthorized + ) end end @@ -70,7 +90,7 @@ describe ErrorTracking::ListIssuesService do it 'raises error' do result = subject.execute - expect(result).to include(status: :error, message: 'not enabled') + expect(result).to include(status: :error, message: 'Error Tracking is not enabled') end end end diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb index ee9c59e3f65..9f25a633deb 100644 --- a/spec/services/error_tracking/list_projects_service_spec.rb +++ b/spec/services/error_tracking/list_projects_service_spec.rb @@ -53,11 +53,11 @@ describe ErrorTracking::ListProjectsService do context 'sentry client raises exception' do before do expect(error_tracking_setting).to receive(:list_sentry_projects) - .and_raise(Sentry::Client::Error, 'Sentry response error: 500') + .and_raise(Sentry::Client::Error, 'Sentry response status code: 500') end it 'returns error response' do - expect(result[:message]).to eq('Sentry response error: 500') + expect(result[:message]).to eq('Sentry response status code: 500') expect(result[:http_status]).to eq(:bad_request) end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 04a62aa454d..ede79b87bcc 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -224,6 +224,18 @@ describe MergeRequests::MergeService do expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) end + it 'logs and saves error if user is not authorized' do + unauthorized_user = create(:user) + project.add_reporter(unauthorized_user) + + service = described_class.new(project, unauthorized_user) + + service.execute(merge_request) + + expect(merge_request.merge_error) + .to eq('You are not allowed to merge this merge request') + end + it 'logs and saves error if there is an PreReceiveError exception' do error_message = 'error message' diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb new file mode 100644 index 00000000000..96f2fde7117 --- /dev/null +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::MergeToRefService do + shared_examples_for 'MergeService for target ref' do + it 'target_ref has the same state of target branch' do + repo = merge_request.target_project.repository + + process_merge_to_ref + merge_service.execute(merge_request) + + ref_commits = repo.commits(merge_request.merge_ref_path, limit: 3) + target_branch_commits = repo.commits(merge_request.target_branch, limit: 3) + + ref_commits.zip(target_branch_commits).each do |ref_commit, target_branch_commit| + expect(ref_commit.parents).to eq(target_branch_commit.parents) + end + end + end + + set(:user) { create(:user) } + 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', + 'should_remove_source_branch' => true) + end + + def process_merge_to_ref + perform_enqueued_jobs do + service.execute(merge_request) + end + end + + it 'writes commit to merge ref' do + repository = project.repository + target_ref = merge_request.merge_ref_path + + expect(repository.ref_exists?(target_ref)).to be(false) + + result = service.execute(merge_request) + + ref_head = repository.commit(target_ref) + + expect(result[:status]).to eq(:success) + expect(result[:commit_id]).to be_present + expect(repository.ref_exists?(target_ref)).to be(true) + expect(ref_head.id).to eq(result[:commit_id]) + end + + it 'does not send any mail' do + expect { process_merge_to_ref }.not_to change { ActionMailer::Base.deliveries.count } + end + + it 'does not change the MR state' do + expect { process_merge_to_ref }.not_to change { merge_request.state } + end + + it 'does not create notes' do + expect { process_merge_to_ref }.not_to change { merge_request.notes.count } + end + + it 'does not delete the source branch' do + expect(DeleteBranchService).not_to receive(:new) + + process_merge_to_ref + end + + it 'returns error when feature is disabled' do + stub_feature_flags(merge_to_tmp_merge_ref_path: false) + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Feature is not enabled') + end + + it 'returns an error when the failing to process the merge' do + allow(project.repository).to receive(:merge_to_ref).and_return(nil) + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Conflicts detected during merge') + end + + context 'commit history comparison with regular MergeService' do + let(:merge_ref_service) do + described_class.new(project, user, {}) + end + + let(:merge_service) do + MergeRequests::MergeService.new(project, user, {}) + end + + context 'when merge commit' do + it_behaves_like 'MergeService for target ref' + end + + context 'when merge commit with squash' do + before do + merge_request.update!(squash: true, source_branch: 'master', target_branch: 'feature') + end + + it_behaves_like 'MergeService for target ref' + end + end + + context 'merge pre-condition checks' do + before do + merge_request.project.update!(merge_method: merge_method) + end + + context 'when semi-linear merge method' do + let(:merge_method) { :rebase_merge } + + it 'return error when MR should be able to fast-forward' do + allow(merge_request).to receive(:should_be_rebased?) { true } + + error_message = 'Fast-forward merge is not possible. Please update your source branch.' + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq(error_message) + end + end + + context 'when fast-forward merge method' do + let(:merge_method) { :ff } + + it 'returns error' do + error_message = "Fast-forward to #{merge_request.merge_ref_path} is currently not supported." + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq(error_message) + end + end + + context 'when MR is not mergeable to ref' do + let(:merge_method) { :merge } + + it 'returns error' do + allow(merge_request).to receive(:mergeable_to_ref?) { false } + + error_message = "Merge request is not mergeable to #{merge_request.merge_ref_path}" + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq(error_message) + end + end + end + + context 'does not close related todos' do + let(:merge_request) { create(:merge_request, assignee: user, author: user) } + let(:project) { merge_request.project } + let!(:todo) do + create(:todo, :assigned, + project: project, + author: user, + user: user, + target: merge_request) + end + + before do + allow(service).to receive(:execute_hooks) + + perform_enqueued_jobs do + service.execute(merge_request) + todo.reload + end + end + + it { expect(todo).not_to be_done } + 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/prometheus/adapter_service_spec.rb b/spec/services/prometheus/adapter_service_spec.rb index 335fc5844aa..505e2935e93 100644 --- a/spec/services/prometheus/adapter_service_spec.rb +++ b/spec/services/prometheus/adapter_service_spec.rb @@ -22,7 +22,15 @@ describe Prometheus::AdapterService do context "prometheus service can't execute queries" do let(:prometheus_service) { double(:prometheus_service, can_query?: false) } - context 'with cluster with prometheus installed' do + context 'with cluster with prometheus not available' do + let!(:prometheus) { create(:clusters_applications_prometheus, :installable, cluster: cluster) } + + it 'returns nil' do + expect(subject.prometheus_adapter).to be_nil + end + end + + context 'with cluster with prometheus available' do let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } it 'returns application handling all environments' do diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 5945a7dc0ad..747e04fb18c 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -102,7 +102,7 @@ describe WebHookService do exception = exception_class.new('Exception message') WebMock.stub_request(:post, project_hook.url).to_raise(exception) - expect(service_instance.execute).to eq({ status: :error, message: exception.message }) + expect(service_instance.execute).to eq({ status: :error, message: exception.to_s }) expect { service_instance.execute }.not_to raise_error end end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index f485eb7b0eb..06bcf4f8013 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -63,7 +63,8 @@ module TestEnv 'after-create-delete-modify-move' => 'ba3faa7', 'with-codeowners' => '219560e', 'submodule_inside_folder' => 'b491b92', - 'png-lfs' => 'fe42f41' + 'png-lfs' => 'fe42f41', + 'sha-starting-with-large-number' => '8426165' }.freeze # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index 7be84838e00..7894484f590 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -1,8 +1,6 @@ RSpec::Matchers.define :require_graphql_authorizations do |*expected| match do |field| - field_definition = field.metadata[:type_class] - expect(field_definition).to respond_to(:required_permissions) - expect(field_definition.required_permissions).to contain_exactly(*expected) + expect(field.metadata[:authorize]).to eq(*expected) end end diff --git a/spec/support/matchers/not_changed_matcher.rb b/spec/support/matchers/not_changed_matcher.rb new file mode 100644 index 00000000000..8ef4694982d --- /dev/null +++ b/spec/support/matchers/not_changed_matcher.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +RSpec::Matchers.define_negated_matcher :not_change, :change diff --git a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb new file mode 100644 index 00000000000..713f0a879c1 --- /dev/null +++ b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb @@ -0,0 +1,5 @@ +RSpec.shared_examples 'issuable state' do + it 'exposes all the existing issuable states' do + expect(described_class.values.keys).to include(*%w[opened closed locked]) + end +end diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb index ec44b99d10e..9dbd1d8e867 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service.rb @@ -39,6 +39,22 @@ shared_examples 'issues move service' do |group| end end + context 'when moving to backlog' do + let(:milestone) { create(:milestone, project: project) } + let!(:backlog) { create(:backlog_list, board: board1) } + + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) } + let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } } + + it 'keeps labels and milestone' do + described_class.new(parent, user, params).execute(issue) + issue.reload + + expect(issue.labels).to contain_exactly(bug, regression) + expect(issue.milestone).to eq(milestone) + end + end + context 'when moving from closed' do let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } } diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb index 9d1efcabb80..cbb4199954a 100644 --- a/spec/views/layouts/_head.html.haml_spec.rb +++ b/spec/views/layouts/_head.html.haml_spec.rb @@ -62,6 +62,14 @@ describe 'layouts/_head' do end end + it 'adds selected syntax highlight stylesheet' do + allow_any_instance_of(PreferencesHelper).to receive(:user_color_scheme).and_return("solarised-light") + + render + + expect(rendered).to match('<link rel="stylesheet" media="all" href="/stylesheets/highlight/themes/solarised-light.css" />') + end + def stub_helper_with_safe_string(method) allow_any_instance_of(PageLayoutHelper).to receive(method) .and_return(%q{foo" http-equiv="refresh}.html_safe) diff --git a/spec/views/projects/commits/_commit.html.haml_spec.rb b/spec/views/projects/commits/_commit.html.haml_spec.rb index 00547e433c4..6bf1b5fd2d0 100644 --- a/spec/views/projects/commits/_commit.html.haml_spec.rb +++ b/spec/views/projects/commits/_commit.html.haml_spec.rb @@ -1,15 +1,15 @@ require 'spec_helper' describe 'projects/commits/_commit.html.haml' do + let(:project) { create(:project, :repository) } + let(:commit) { project.repository.commit(ref) } + before do allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings) end - context 'with a singed commit' do - let(:project) { create(:project, :repository) } - let(:repository) { project.repository } + context 'with a signed commit' do let(:ref) { GpgHelpers::SIGNED_COMMIT_SHA } - let(:commit) { repository.commit(ref) } it 'does not display a loading spinner for GPG status' do render partial: 'projects/commits/commit', locals: { @@ -23,4 +23,55 @@ describe 'projects/commits/_commit.html.haml' do end end end + + context 'with ci status' do + let(:ref) { 'master' } + let(:user) { create(:user) } + + before do + allow(view).to receive(:current_user).and_return(user) + + project.add_developer(user) + + create( + :ci_empty_pipeline, + ref: 'master', + sha: commit.id, + status: 'success', + project: project + ) + end + + context 'when pipelines are disabled' do + before do + allow(project).to receive(:builds_enabled?).and_return(false) + end + + it 'does not display a ci status icon' do + render partial: 'projects/commits/commit', locals: { + project: project, + ref: ref, + commit: commit + } + + expect(rendered).not_to have_css('.ci-status-link') + end + end + + context 'when pipelines are enabled' do + before do + allow(project).to receive(:builds_enabled?).and_return(true) + end + + it 'does display a ci status icon when pipelines are enabled' do + render partial: 'projects/commits/commit', locals: { + project: project, + ref: ref, + commit: commit + } + + expect(rendered).to have_css('.ci-status-link') + end + end + end end diff --git a/spec/views/projects/issues/show.html.haml_spec.rb b/spec/views/projects/issues/show.html.haml_spec.rb index ff88efd0e31..1d9c6d36ad7 100644 --- a/spec/views/projects/issues/show.html.haml_spec.rb +++ b/spec/views/projects/issues/show.html.haml_spec.rb @@ -21,12 +21,24 @@ describe 'projects/issues/show' do allow(issue).to receive(:closed?).and_return(true) end - it 'shows "Closed (moved)" if an issue has been moved' do - allow(issue).to receive(:moved?).and_return(true) + context 'when the issue was moved' do + let(:new_issue) { create(:issue, project: project, author: user) } - render + before do + issue.moved_to = new_issue + end + + it 'shows "Closed (moved)" if an issue has been moved' do + render + + expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)') + end + + it 'links "moved" to the new issue the original issue was moved to' do + render - expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)') + expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved') + end end it 'shows "Closed" if an issue has not been moved' do diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb index acd8da11d8d..ccb26849e67 100644 --- a/spec/workers/build_finished_worker_spec.rb +++ b/spec/workers/build_finished_worker_spec.rb @@ -26,5 +26,24 @@ describe BuildFinishedWorker do .not_to raise_error end end + + it 'schedules a ChatNotification job for a chat build' do + build = create(:ci_build, :success, pipeline: create(:ci_pipeline, source: :chat)) + + expect(ChatNotificationWorker) + .to receive(:perform_async) + .with(build.id) + + described_class.new.perform(build.id) + end + + it 'does not schedule a ChatNotification job for a regular build' do + build = create(:ci_build, :success, pipeline: create(:ci_pipeline)) + + expect(ChatNotificationWorker) + .not_to receive(:perform_async) + + described_class.new.perform(build.id) + end end end diff --git a/spec/workers/chat_notification_worker_spec.rb b/spec/workers/chat_notification_worker_spec.rb new file mode 100644 index 00000000000..91695674f5d --- /dev/null +++ b/spec/workers/chat_notification_worker_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ChatNotificationWorker do + let(:worker) { described_class.new } + let(:chat_build) do + create(:ci_build, pipeline: create(:ci_pipeline, source: :chat)) + end + + describe '#perform' do + it 'does nothing when the build no longer exists' do + expect(worker).not_to receive(:send_response) + + worker.perform(-1) + end + + it 'sends a response for an existing build' do + expect(worker) + .to receive(:send_response) + .with(an_instance_of(Ci::Build)) + + worker.perform(chat_build.id) + end + + it 'reschedules the job if the trace sections could not be found' do + expect(worker) + .to receive(:send_response) + .and_raise(Gitlab::Chat::Output::MissingBuildSectionError) + + expect(described_class) + .to receive(:perform_in) + .with(described_class::RESCHEDULE_INTERVAL, chat_build.id) + + worker.perform(chat_build.id) + end + end + + describe '#send_response' do + context 'when a responder could not be found' do + it 'does nothing' do + expect(Gitlab::Chat::Responder) + .to receive(:responder_for) + .with(chat_build) + .and_return(nil) + + expect(worker.send_response(chat_build)).to be_nil + end + end + + context 'when a responder could be found' do + let(:responder) { double(:responder) } + + before do + allow(Gitlab::Chat::Responder) + .to receive(:responder_for) + .with(chat_build) + .and_return(responder) + end + + it 'sends the response for a succeeded build' do + output = double(:output, to_s: 'this is the build output') + + expect(chat_build) + .to receive(:success?) + .and_return(true) + + expect(responder) + .to receive(:success) + .with(an_instance_of(String)) + + expect(Gitlab::Chat::Output) + .to receive(:new) + .with(chat_build) + .and_return(output) + + worker.send_response(chat_build) + end + + it 'sends the response for a failed build' do + expect(chat_build) + .to receive(:success?) + .and_return(false) + + expect(responder).to receive(:failure) + + worker.send_response(chat_build) + end + end + end +end diff --git a/vendor/project_templates/dotnetcore.tar.gz b/vendor/project_templates/dotnetcore.tar.gz Binary files differnew file mode 100644 index 00000000000..b5dae407f83 --- /dev/null +++ b/vendor/project_templates/dotnetcore.tar.gz diff --git a/vendor/project_templates/nfgitbook.tar.gz b/vendor/project_templates/nfgitbook.tar.gz Binary files differnew file mode 100644 index 00000000000..e10873e1a9e --- /dev/null +++ b/vendor/project_templates/nfgitbook.tar.gz diff --git a/vendor/project_templates/nfhexo.tar.gz b/vendor/project_templates/nfhexo.tar.gz Binary files differnew file mode 100644 index 00000000000..25632f241cc --- /dev/null +++ b/vendor/project_templates/nfhexo.tar.gz diff --git a/vendor/project_templates/nfhugo.tar.gz b/vendor/project_templates/nfhugo.tar.gz Binary files differnew file mode 100644 index 00000000000..14e6289b841 --- /dev/null +++ b/vendor/project_templates/nfhugo.tar.gz diff --git a/vendor/project_templates/nfjekyll.tar.gz b/vendor/project_templates/nfjekyll.tar.gz Binary files differnew file mode 100644 index 00000000000..3b93f8661b5 --- /dev/null +++ b/vendor/project_templates/nfjekyll.tar.gz diff --git a/vendor/project_templates/nfplainhtml.tar.gz b/vendor/project_templates/nfplainhtml.tar.gz Binary files differnew file mode 100644 index 00000000000..cdf5ea9fe12 --- /dev/null +++ b/vendor/project_templates/nfplainhtml.tar.gz diff --git a/yarn.lock b/yarn.lock index 0111b583404..ea822b23113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -653,10 +653,10 @@ eslint-plugin-promise "^4.0.1" eslint-plugin-vue "^5.0.0" -"@gitlab/svgs@^1.52.0": - version "1.52.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.52.0.tgz#870b0112a18b10d2cde5470a48b05025193cd207" - integrity sha512-xqDYIaSY1MJuCa7lRIDTTPoXn6x57So2qchxwmELE+SAJxlYlpYgDKCNWcGawhyTZRDZuG/qFBWp0sMeTQD//A== +"@gitlab/svgs@^1.54.0": + version "1.54.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf" + integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ== "@gitlab/ui@^2.0.4": version "2.0.4" @@ -6371,6 +6371,11 @@ jest-snapshot@^23.6.0: pretty-format "^23.6.0" semver "^5.5.0" +jest-transform-graphql@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/jest-transform-graphql/-/jest-transform-graphql-2.1.0.tgz#903cb66bb27bc2772fd3e5dd4f7e9b57230f5829" + integrity sha1-kDy2a7J7wncv0+XdT36bVyMPWCk= + jest-util@^23.4.0: version "23.4.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561" |