diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-18 12:10:16 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-18 12:10:16 +0000 |
commit | f10eb9ebaefb0d6ff4ee7552dbf127dc70aaf27d (patch) | |
tree | ddf0000b12ccb57f3926785976c4ccd6eaf3aac3 | |
parent | 89eff770d213e684b5fa4df121cb51a059e8d263 (diff) | |
download | gitlab-ce-f10eb9ebaefb0d6ff4ee7552dbf127dc70aaf27d.tar.gz |
Add latest changes from gitlab-org/gitlab@master
45 files changed, 811 insertions, 128 deletions
diff --git a/.gitlab/issue_templates/QA Failure.md b/.gitlab/issue_templates/QA Failure.md index 2a8b1b2d2f9..772f363ae31 100644 --- a/.gitlab/issue_templates/QA Failure.md +++ b/.gitlab/issue_templates/QA Failure.md @@ -68,10 +68,10 @@ a nightly pipeline, select ~"found:nightly". <!-- https://about.gitlab.com/handbook/engineering/quality/guidelines/#priorities: -- ~P1: Tests that are needed to verify fundamental GitLab functionality. -- ~P2: Tests that deal with external integrations which may take a longer time to debug and fix. +- ~P::1: Tests that are needed to verify fundamental GitLab functionality. +- ~P::2: Tests that deal with external integrations which may take a longer time to debug and fix. --> -/label ~P +/label ~P:: -<!-- Select the current milestone if ~P1 or the next milestone if ~P2. --> +<!-- Select the current milestone if ~P::1 or the next milestone if ~P::2. --> /milestone % diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c2e8bfa1140..b1eb0e59e77 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -877,7 +877,6 @@ Rails/SaveBang: - 'ee/spec/support/shared_examples/requests/api/project_approval_rules_api_shared_examples.rb' - 'ee/spec/support/shared_examples/services/build_execute_shared_examples.rb' - 'ee/spec/support/shared_examples/services/issue_epic_shared_examples.rb' - - 'ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb' - 'ee/spec/workers/adjourned_project_deletion_worker_spec.rb' - 'ee/spec/workers/clear_shared_runners_minutes_worker_spec.rb' - 'ee/spec/workers/create_github_webhook_worker_spec.rb' @@ -1342,7 +1341,3 @@ Rails/SaveBang: - 'spec/tasks/gitlab/web_hook_rake_spec.rb' - 'spec/uploaders/file_uploader_spec.rb' - 'spec/uploaders/object_storage_spec.rb' - - 'spec/views/notify/changed_milestone_email.html.haml_spec.rb' - - 'spec/views/projects/imports/new.html.haml_spec.rb' - - 'spec/views/projects/merge_requests/show.html.haml_spec.rb' - - 'spec/views/shared/_label_row.html.haml_spec.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 212e34658c2..379a56d288b 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -02a62b036da8980f536c12a5f163f4d0bba54c55 +8302f636d0a3f1b83cb7e5420b2720e83e564306 diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 7fe50d36c0c..ad049c4449e 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -149,9 +149,14 @@ export default { 'removePlaceholderNotes', 'toggleResolveNote', 'removeConvertedDiscussion', + 'expandDiscussion', ]), showReplyForm() { this.isReplying = true; + + if (!this.discussion.expanded) { + this.expandDiscussion({ discussionId: this.discussion.id }); + } }, cancelReplyForm(shouldConfirm, isDirty) { if (shouldConfirm && isDirty) { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index b9398bd00f9..2f28361f62c 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -166,6 +166,14 @@ color: inherit; } + // TODO remove this class once we can generate a correct hover utility from `gitlab/ui`, + // see here: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39286#note_396767000 + .btn-link-hover:hover { + * { + @include gl-text-blue-800; + } + } + .issuable-header-text { margin-top: 7px; } diff --git a/app/presenters/snippet_blob_presenter.rb b/app/presenters/snippet_blob_presenter.rb index 6557e3a4414..abe95f5c44d 100644 --- a/app/presenters/snippet_blob_presenter.rb +++ b/app/presenters/snippet_blob_presenter.rb @@ -4,7 +4,6 @@ class SnippetBlobPresenter < BlobPresenter include GitlabRoutingHelper def rich_data - return if blob.binary? return unless blob.rich_viewer render_rich_partial @@ -17,9 +16,11 @@ class SnippetBlobPresenter < BlobPresenter end def raw_path - return gitlab_raw_snippet_blob_path(snippet, blob.path) if snippet_multiple_files? + snippet_blob_raw_route(only_path: true) + end - gitlab_raw_snippet_path(snippet) + def raw_url + snippet_blob_raw_route end private @@ -38,7 +39,7 @@ class SnippetBlobPresenter < BlobPresenter def render_rich_partial renderer.render("projects/blob/viewers/_#{blob.rich_viewer.partial_name}", - locals: { viewer: blob.rich_viewer, blob: blob, blob_raw_path: raw_path }, + locals: { viewer: blob.rich_viewer, blob: blob, blob_raw_path: raw_path, blob_raw_url: raw_url }, layout: false) end @@ -49,4 +50,10 @@ class SnippetBlobPresenter < BlobPresenter ApplicationController.renderer.new('warden' => proxy) end + + def snippet_blob_raw_route(only_path: false) + return gitlab_raw_snippet_blob_url(snippet, blob.path, only_path: only_path) if snippet_multiple_files? + + gitlab_raw_snippet_url(snippet, only_path: only_path) + end end diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 94482717d0e..e1d1df9de1a 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -160,15 +160,12 @@ .loading.hide .spinner.spinner-md - - if profile_tabs.empty? - .row - .col-12 - .svg-content - = image_tag 'illustrations/profile_private_mode.svg' - .col-12.text-center - .text-content - %h4 - - if @user.blocked? - = s_('UserProfile|This user is blocked') - - else - = s_('UserProfile|This user has a private profile') + - if profile_tabs.empty? + .svg-content + = image_tag 'illustrations/profile_private_mode.svg' + .text-content.text-center + %h4 + - if @user.blocked? + = s_('UserProfile|This user is blocked') + - else + = s_('UserProfile|This user has a private profile') diff --git a/changelogs/unreleased/210550-conan-missing-files.yml b/changelogs/unreleased/210550-conan-missing-files.yml new file mode 100644 index 00000000000..f5d8d13e4a7 --- /dev/null +++ b/changelogs/unreleased/210550-conan-missing-files.yml @@ -0,0 +1,5 @@ +--- +title: Conan packages allow for conan_sources.tgz and conan_export.tgz files +merge_request: 39559 +author: +type: fixed diff --git a/changelogs/unreleased/237864-fj-return-snippet-binary-blob-content.yml b/changelogs/unreleased/237864-fj-return-snippet-binary-blob-content.yml new file mode 100644 index 00000000000..4ca5683a7f6 --- /dev/null +++ b/changelogs/unreleased/237864-fj-return-snippet-binary-blob-content.yml @@ -0,0 +1,5 @@ +--- +title: Return snippet binary blob content in GraphQL +merge_request: 39583 +author: +type: changed diff --git a/changelogs/unreleased/29564-showing-horizontal-scroll-bar-in-private-profile.yml b/changelogs/unreleased/29564-showing-horizontal-scroll-bar-in-private-profile.yml new file mode 100644 index 00000000000..c0907170fb6 --- /dev/null +++ b/changelogs/unreleased/29564-showing-horizontal-scroll-bar-in-private-profile.yml @@ -0,0 +1,5 @@ +--- +title: Fix horizontal scrolling on blocked/private profile pages +merge_request: 39568 +author: +type: fixed diff --git a/changelogs/unreleased/ph-235529-replyExpandsCollapsedDiscussion.yml b/changelogs/unreleased/ph-235529-replyExpandsCollapsedDiscussion.yml new file mode 100644 index 00000000000..f43c77d68ca --- /dev/null +++ b/changelogs/unreleased/ph-235529-replyExpandsCollapsedDiscussion.yml @@ -0,0 +1,5 @@ +--- +title: Fixed discussion not expanding when replying to a collapsed discussion +merge_request: 39571 +author: +type: fixed diff --git a/changelogs/unreleased/rails-save-bang-11.yml b/changelogs/unreleased/rails-save-bang-11.yml new file mode 100644 index 00000000000..8e270df08cd --- /dev/null +++ b/changelogs/unreleased/rails-save-bang-11.yml @@ -0,0 +1,5 @@ +--- +title: Refactor spec/views/* and ee/spec/views/* to fix Rails/SaveBang Cop +merge_request: 38981 +author: Rajendra Kadam +type: fixed diff --git a/changelogs/unreleased/sy-move-alert-embeds-to-core.yml b/changelogs/unreleased/sy-move-alert-embeds-to-core.yml new file mode 100644 index 00000000000..be17f7ae68b --- /dev/null +++ b/changelogs/unreleased/sy-move-alert-embeds-to-core.yml @@ -0,0 +1,5 @@ +--- +title: Move gitlab-managed alerts embeds to core as documented +merge_request: 39509 +author: +type: fixed diff --git a/db/migrate/20200811055018_remove_not_null_constraint_on_type_from_audit_events.rb b/db/migrate/20200811055018_remove_not_null_constraint_on_type_from_audit_events.rb index 629f357cd19..25c3ef71655 100644 --- a/db/migrate/20200811055018_remove_not_null_constraint_on_type_from_audit_events.rb +++ b/db/migrate/20200811055018_remove_not_null_constraint_on_type_from_audit_events.rb @@ -7,7 +7,6 @@ class RemoveNotNullConstraintOnTypeFromAuditEvents < ActiveRecord::Migration[6.0 disable_ddl_transaction! - # rubocop:disable Migration/WithLockRetriesDisallowedMethod # To avoid deadlock on audit_event and audit_event_part... since there is a trigger to insert record from audit_events # to audit_events_part..., we need to ensure each ALTER TABLE command run in its own transaction. def up @@ -19,7 +18,6 @@ class RemoveNotNullConstraintOnTypeFromAuditEvents < ActiveRecord::Migration[6.0 change_column_null :audit_events, :type, true end end - # rubocop:enable Migration/WithLockRetriesDisallowedMethod def down # no-op -- null values might be added after this constraint is removed. diff --git a/doc/README.md b/doc/README.md index 5e5d76374a2..49170b75dbd 100644 --- a/doc/README.md +++ b/doc/README.md @@ -157,6 +157,7 @@ The following documentation relates to the DevOps **Create** stage: | [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy your static website with GitLab Pages. | | [Groups](user/group/index.md) and [Subgroups](user/group/subgroups/index.md) | Organize your projects in groups. | | [Issues Analytics](user/group/issues_analytics/index.md) **(PREMIUM)** | Check how many issues were created per month. | +| [Merge Request Analytics](user/analytics/merge_request_analytics.md) **(PREMIUM)** | Check your throughput productivity - how many merge requests were merged per month. | | [Projects](user/project/index.md), including [project access](public_access/public_access.md)<br/>and [settings](user/project/settings/index.md) | Host source code, and control your project's visibility and set configuration. | | [Search through GitLab](user/search/index.md) | Search for issues, merge requests, projects, groups, and todos. | | [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. | diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index da5caf3966f..15014fffd01 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -135,3 +135,22 @@ The LDAP check Rake task will test the bind DN and password credentials (if configured) and will list a sample of LDAP users. This task is also executed as part of the `gitlab:check` task, but can run independently. See [LDAP Rake Tasks - LDAP Check](ldap.md#check) for details. + +## Troubleshooting + +The following are solutions to problems you might discover using the Rake tasks documented +above. + +### Dangling commits + +`gitlab:git:fsck` can find dangling commits. To fix them, try +[manually triggering housekeeping](../housekeeping.md#manual-housekeeping) +for the affected project(s). + +If the issue persists, try triggering `gc` via the +[Rails Console](../troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session): + +```ruby +p = Project.find_by_path("project-name") +Projects::HousekeepingService.new(p, :gc).execute +``` diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index 387867309b8..43c50002779 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -1628,6 +1628,33 @@ type CiStageEdge { node: CiStage } +type ClusterAgent { + """ + Timestamp the cluster agent was created + """ + createdAt: Time + + """ + ID of the cluster agent + """ + id: ID! + + """ + Name of the cluster agent + """ + name: String + + """ + The project this cluster agent is associated with + """ + project: Project + + """ + Timestamp the cluster agent was updated + """ + updatedAt: Time +} + type Commit { """ Author of the commit @@ -2261,6 +2288,46 @@ type CreateBranchPayload { } """ +Autogenerated input type of CreateClusterAgent +""" +input CreateClusterAgentInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Name of the cluster agent + """ + name: String! + + """ + Full path of the associated project for this cluster agent + """ + projectPath: ID! +} + +""" +Autogenerated return type of CreateClusterAgent +""" +type CreateClusterAgentPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Cluster agent created after mutation + """ + clusterAgent: ClusterAgent + + """ + Errors encountered during execution of the mutation. + """ + errors: [String!]! +} + +""" Autogenerated input type of CreateDiffNote """ input CreateDiffNoteInput { @@ -9519,6 +9586,7 @@ type Mutation { createAlertIssue(input: CreateAlertIssueInput!): CreateAlertIssuePayload createAnnotation(input: CreateAnnotationInput!): CreateAnnotationPayload createBranch(input: CreateBranchInput!): CreateBranchPayload + createClusterAgent(input: CreateClusterAgentInput!): CreateClusterAgentPayload createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload createEpic(input: CreateEpicInput!): CreateEpicPayload createImageDiffNote(input: CreateImageDiffNoteInput!): CreateImageDiffNotePayload diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index b2d2bd61d5b..f4282facd30 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -4433,6 +4433,93 @@ }, { "kind": "OBJECT", + "name": "ClusterAgent", + "description": null, + "fields": [ + { + "name": "createdAt", + "description": "Timestamp the cluster agent was created", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Time", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": "ID of the cluster agent", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "Name of the cluster agent", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "project", + "description": "The project this cluster agent is associated with", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Project", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updatedAt", + "description": "Timestamp the cluster agent was updated", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "Time", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", "name": "Commit", "description": null, "fields": [ @@ -6060,6 +6147,122 @@ }, { "kind": "INPUT_OBJECT", + "name": "CreateClusterAgentInput", + "description": "Autogenerated input type of CreateClusterAgent", + "fields": null, + "inputFields": [ + { + "name": "projectPath", + "description": "Full path of the associated project for this cluster agent", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "name", + "description": "Name of the cluster agent", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CreateClusterAgentPayload", + "description": "Autogenerated return type of CreateClusterAgent", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "clusterAgent", + "description": "Cluster agent created after mutation", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "ClusterAgent", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "errors", + "description": "Errors encountered during execution of the mutation.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", "name": "CreateDiffNoteInput", "description": "Autogenerated input type of CreateDiffNote", "fields": null, @@ -26994,6 +27197,33 @@ "deprecationReason": null }, { + "name": "createClusterAgent", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CreateClusterAgentInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "CreateClusterAgentPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "createDiffNote", "description": null, "args": [ diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index dd2fa5e191b..b766007c7de 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -264,6 +264,16 @@ Autogenerated return type of BoardListUpdateLimitMetrics | --- | ---- | ---------- | | `name` | String | Name of the stage | +## ClusterAgent + +| Name | Type | Description | +| --- | ---- | ---------- | +| `createdAt` | Time | Timestamp the cluster agent was created | +| `id` | ID! | ID of the cluster agent | +| `name` | String | Name of the cluster agent | +| `project` | Project | The project this cluster agent is associated with | +| `updatedAt` | Time | Timestamp the cluster agent was updated | + ## Commit | Name | Type | Description | @@ -360,6 +370,16 @@ Autogenerated return type of CreateBranch | `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. | +## CreateClusterAgentPayload + +Autogenerated return type of CreateClusterAgent + +| Name | Type | Description | +| --- | ---- | ---------- | +| `clientMutationId` | String | A unique identifier for the client performing the mutation. | +| `clusterAgent` | ClusterAgent | Cluster agent created after mutation | +| `errors` | String! => Array | Errors encountered during execution of the mutation. | + ## CreateDiffNotePayload Autogenerated return type of CreateDiffNote diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md index 9feaa485bd2..76175cb7b66 100644 --- a/doc/development/contributing/issue_workflow.md +++ b/doc/development/contributing/issue_workflow.md @@ -49,8 +49,8 @@ Most issues will have labels for at least one of the following: - Team: `~"Technical Writing"`, `~Delivery` - Specialization: `~frontend`, `~backend`, `~documentation` - Release Scoping: `~Deliverable`, `~Stretch`, `~"Next Patch Release"` -- Priority: `~P1`, `~P2`, `~P3`, `~P4` -- Severity: ~`S1`, `~S2`, `~S3`, `~S4` +- Priority: `~P::1`, `~P::2`, `~P::3`, `~P::4` +- Severity: ~`S::1`, `~S::2`, `~S::3`, `~S::4` All labels, their meaning and priority are defined on the [labels page](https://gitlab.com/gitlab-org/gitlab/-/labels). @@ -275,10 +275,10 @@ or ~"Stretch". Any open issue for a previous milestone should be labeled We have the following priority labels: -- ~P1 -- ~P2 -- ~P3 -- ~P4 +- ~P::1 +- ~P::2 +- ~P::3 +- ~P::4 Please refer to the issue triage [priority label](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#priority) section in our handbook to see how it's used. @@ -286,10 +286,10 @@ Please refer to the issue triage [priority label](https://about.gitlab.com/handb We have the following severity labels: -- ~S1 -- ~S2 -- ~S3 -- ~S4 +- ~S::1 +- ~S::2 +- ~S::3 +- ~S::4 Please refer to the issue triage [severity label](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#severity) section in our handbook to see how it's used. diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md index ed6d08a9894..b3829a82d59 100644 --- a/doc/development/merge_request_performance_guidelines.md +++ b/doc/development/merge_request_performance_guidelines.md @@ -211,7 +211,7 @@ For keeping transaction as minimal as possible, please consider using `AfterComm module or `after_commit` AR hook. Here is [an example](https://gitlab.com/gitlab-org/gitlab/-/issues/36154#note_247228859) -that one request to Gitaly instance during transaction triggered a P1 issue. +that one request to Gitaly instance during transaction triggered a P::1 issue. ## Eager Loading diff --git a/doc/user/analytics/img/merge_request_analytics_v13_3.png b/doc/user/analytics/img/merge_request_analytics_v13_3.png Binary files differnew file mode 100644 index 00000000000..f90f3625a51 --- /dev/null +++ b/doc/user/analytics/img/merge_request_analytics_v13_3.png diff --git a/doc/user/analytics/index.md b/doc/user/analytics/index.md index 9e3d2c6039e..96aff919ccd 100644 --- a/doc/user/analytics/index.md +++ b/doc/user/analytics/index.md @@ -38,6 +38,7 @@ The following analytics features are available at the project level: - [Code Review](code_review_analytics.md). **(STARTER)** - [Insights](../group/insights/index.md). **(ULTIMATE)** - [Issues](../group/issues_analytics/index.md). **(PREMIUM)** +- [Merge Request](merge_request_analytics.md). **(STARTER)** - [Repository](repository_analytics.md). - [Value Stream](value_stream_analytics.md), enabled with the `cycle_analytics` [feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-locally-in-development). **(STARTER)** diff --git a/doc/user/analytics/merge_request_analytics.md b/doc/user/analytics/merge_request_analytics.md new file mode 100644 index 00000000000..01295ae888b --- /dev/null +++ b/doc/user/analytics/merge_request_analytics.md @@ -0,0 +1,42 @@ +--- +description: "Merge Request Analytics help you understand the efficiency of your code review process, and the productivity of your team." # Up to ~200 chars long. They will be displayed in Google Search snippets. It may help to write the page intro first, and then reuse it here. +stage: Manage +group: Analytics +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers +--- + + +# Merge Request Analytics **(STARTER)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229045) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.3. + +Merge Request Analytics helps you understand the efficiency of your code review process, and the productivity of your team. + +## Overview + +Merge Request Analytics displays information about all accepted merge requests. + +The Throughput chart shows the number of completed merge requests, by month. Merge request throughput is +a common measure of productivity in software engineering. Although imperfect, the average throughput can +be a meaningful benchmark of your team's overall productivity. + +To access Merge Request Analytics, from your project's menu, go to **Analytics > Merge Request**. + +![Merge Request Analytics](img/merge_request_analytics_v13_3.png "Merge Request Analytics - Throughput chart") + +## Use cases + +This feature is designed for [development team leaders](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#delaney-development-team-lead) +and others who want to understand broad patterns in code review and productivity. + +You can use Merge Request Analytics to expose when your team is most and least productive, and +identify improvements that might substantially accelerate your development cycle. + +Merge Request Analytics could be used when: + +- You want to know if you were more productive this month than last month, or 12 months ago. +- You want to drill into low- or high-productivity months to understand the work that took place. + +## Permissions + +- On [Starter or Bronze tier](https://about.gitlab.com/pricing/) and above. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 39b61bd7bee..0b541eb248a 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -168,6 +168,7 @@ The following table depicts the various user permission levels in a project. | View Code Review analytics **(STARTER)** | | ✓ | ✓ | ✓ | ✓ | | View Insights **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ | | View Issues analytics **(PREMIUM)** | ✓ | ✓ | ✓ | ✓ | ✓ | +| View Merge Request analytics **(STARTER)** | ✓ | ✓ | ✓ | ✓ | ✓ | | View Repository analytics | | ✓ | ✓ | ✓ | ✓ | | View Value Stream analytics | ✓ | ✓ | ✓ | ✓ | ✓ | diff --git a/lib/api/conan_packages.rb b/lib/api/conan_packages.rb index 1d941e422a7..6888929874f 100644 --- a/lib/api/conan_packages.rb +++ b/lib/api/conan_packages.rb @@ -189,9 +189,7 @@ module API authorize!(:read_package, project) status 200 - upload_urls = package_upload_urls(::Packages::Conan::FileMetadatum::PACKAGE_FILES) - - present upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls + present package_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls end desc 'Recipe Upload Urls' do @@ -202,9 +200,7 @@ module API authorize!(:read_package, project) status 200 - upload_urls = recipe_upload_urls(::Packages::Conan::FileMetadatum::RECIPE_FILES) - - present upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls + present recipe_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls end desc 'Delete Package' do diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb index 30e690a5a1d..a5fde1af41e 100644 --- a/lib/api/helpers/packages/conan/api_helpers.rb +++ b/lib/api/helpers/packages/conan/api_helpers.rb @@ -28,22 +28,30 @@ module API present_download_urls(::API::Entities::ConanPackage::ConanRecipeManifest, &:recipe_urls) end - def recipe_upload_urls(file_names) + def recipe_upload_urls { upload_urls: Hash[ - file_names.collect do |file_name| + file_names.select(&method(:recipe_file?)).map do |file_name| [file_name, recipe_file_upload_url(file_name)] end ] } end - def package_upload_urls(file_names) + def package_upload_urls { upload_urls: Hash[ - file_names.collect do |file_name| + file_names.select(&method(:package_file?)).map do |file_name| [file_name, package_file_upload_url(file_name)] end ] } end + def recipe_file?(file_name) + file_name.in?(::Packages::Conan::FileMetadatum::RECIPE_FILES) + end + + def package_file?(file_name) + file_name.in?(::Packages::Conan::FileMetadatum::PACKAGE_FILES) + end + def package_file_upload_url(file_name) expose_url( api_v4_packages_conan_v1_files_package_path( @@ -130,6 +138,14 @@ module API end end + def file_names + json_payload = Gitlab::Json.parse(request.body.string) + + bad_request!(nil) unless json_payload.is_a?(Hash) + + json_payload.keys + end + def create_package_file_with_type(file_type, current_package) unless params['file.size'] == 0 # conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0 diff --git a/lib/banzai/filter/inline_alert_metrics_filter.rb b/lib/banzai/filter/inline_alert_metrics_filter.rb new file mode 100644 index 00000000000..a6140d1ac81 --- /dev/null +++ b/lib/banzai/filter/inline_alert_metrics_filter.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Banzai + module Filter + # HTML filter that inserts a placeholder element for each + # reference to an alert dashboard. + class InlineAlertMetricsFilter < ::Banzai::Filter::InlineEmbedsFilter + include ::Gitlab::Routing + # Search params for selecting alert metrics links. A few + # simple checks is enough to boost performance without + # the cost of doing a full regex match. + def xpath_search + "descendant-or-self::a[contains(@href,'metrics_dashboard') and \ + contains(@href,'prometheus/alerts') and \ + starts-with(@href, '#{gitlab_domain}')]" + end + + # Regular expression matching alert dashboard urls + def link_pattern + ::Gitlab::Metrics::Dashboard::Url.alert_regex + end + + private + + # Endpoint FE should hit to collect the appropriate + # chart information + def metrics_dashboard_url(params) + metrics_dashboard_namespace_project_prometheus_alert_url( + params['namespace'], + params['project'], + params['alert'], + embedded: true, + format: :json, + **query_params(params['url']) + ) + end + + # Parses query params out from full url string into hash. + # + # Ex) 'https://<root>/<project>/metrics_dashboard?title=Title&group=Group' + # --> { title: 'Title', group: 'Group' } + def query_params(url) + ::Gitlab::Metrics::Dashboard::Url.parse_query(url) + end + end + end +end diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb index 7f98a52d421..2259115acfc 100644 --- a/lib/banzai/filter/inline_metrics_redactor_filter.rb +++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb @@ -81,6 +81,10 @@ module Banzai Route.new( ::Gitlab::Metrics::Dashboard::Url.clusters_regex, :read_cluster + ), + Route.new( + ::Gitlab::Metrics::Dashboard::Url.alert_regex, + :read_prometheus_alerts ) ] end @@ -147,5 +151,3 @@ module Banzai end end end - -Banzai::Filter::InlineMetricsRedactorFilter.prepend_if_ee('EE::Banzai::Filter::InlineMetricsRedactorFilter') diff --git a/lib/gitlab/metrics/dashboard/url.rb b/lib/gitlab/metrics/dashboard/url.rb index 94f586614f7..160ecfb85c9 100644 --- a/lib/gitlab/metrics/dashboard/url.rb +++ b/lib/gitlab/metrics/dashboard/url.rb @@ -43,6 +43,39 @@ module Gitlab end end + # Matches dashboard urls for a metric chart embed + # for cluster metrics + # + # EX - https://<host>/<namespace>/<project>/-/clusters/<cluster_id>/?group=Cluster%20Health&title=Memory%20Usage&y_label=Memory%20(GiB) + def clusters_regex + strong_memoize(:clusters_regex) do + regex_for_project_metrics( + %r{ + /clusters + /(?<cluster_id>\d+) + /? + }x + ) + end + end + + # Matches dashboard urls for a metric chart embed + # for a specifc firing GitLab alert + # + # EX - https://<host>/<namespace>/<project>/prometheus/alerts/<alert_id>/metrics_dashboard + def alert_regex + strong_memoize(:alert_regex) do + regex_for_project_metrics( + %r{ + /prometheus + /alerts + /(?<alert>\d+) + /metrics_dashboard + }x + ) + end + end + # Parses query params out from full url string into hash. # # Ex) 'https://<root>/<project>/<environment>/metrics?title=Title&group=Group' @@ -60,22 +93,6 @@ module Gitlab Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_environment_url(*args) end - # Matches dashboard urls for a metric chart embed - # for cluster metrics - # - # EX - https://<host>/<namespace>/<project>/-/clusters/<cluster_id>/?group=Cluster%20Health&title=Memory%20Usage&y_label=Memory%20(GiB) - def clusters_regex - strong_memoize(:clusters_regex) do - regex_for_project_metrics( - %r{ - /clusters - /(?<cluster_id>\d+) - /? - }x - ) - end - end - private def regex_for_project_metrics(path_suffix_pattern) @@ -107,5 +124,3 @@ module Gitlab end end end - -Gitlab::Metrics::Dashboard::Url.extend_if_ee('::EE::Gitlab::Metrics::Dashboard::Url') diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 89f19cf86fc..1e1e0d856b7 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -3,7 +3,7 @@ module Gitlab module Regex module Packages - CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt].freeze + CONAN_RECIPE_FILES = %w[conanfile.py conanmanifest.txt conan_sources.tgz conan_export.tgz].freeze CONAN_PACKAGE_FILES = %w[conaninfo.txt conanmanifest.txt conan_package.tgz].freeze def conan_file_name_regex diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c76c21a7fe9..ff2627e69a5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5105,6 +5105,12 @@ msgstr "" msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter" msgstr "" +msgid "ClusterAgent|This feature is only available for premium plans" +msgstr "" + +msgid "ClusterAgent|You have insufficient permissions to create a cluster agent for this project" +msgstr "" + msgid "ClusterIntegration| This will permanently delete the following resources: <ul> <li>All installed applications and related resources</li> <li>The <code>gitlab-managed-apps</code> namespace</li> <li>Any project namespaces</li> <li><code>clusterroles</code></li> <li><code>clusterrolebindings</code></li> </ul>" msgstr "" diff --git a/rubocop/cop/migration/with_lock_retries_disallowed_method.rb b/rubocop/cop/migration/with_lock_retries_disallowed_method.rb index 9bf81e7db0c..a89c33c298b 100644 --- a/rubocop/cop/migration/with_lock_retries_disallowed_method.rb +++ b/rubocop/cop/migration/with_lock_retries_disallowed_method.rb @@ -18,6 +18,7 @@ module RuboCop remove_column execute change_column_default + change_column_null remove_foreign_key_if_exists remove_foreign_key_without_error table_exists? diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js index 817a6b09056..1c6603899d3 100644 --- a/spec/frontend/notes/components/noteable_discussion_spec.js +++ b/spec/frontend/notes/components/noteable_discussion_spec.js @@ -89,6 +89,23 @@ describe('noteable_discussion component', () => { }); }); + it('should expand discussion', async () => { + const expandDiscussion = jest.fn(); + const discussion = { ...discussionMock }; + discussion.expanded = false; + + wrapper.setProps({ discussion }); + wrapper.setMethods({ expandDiscussion }); + + await wrapper.vm.$nextTick(); + + wrapper.vm.showReplyForm(); + + await wrapper.vm.$nextTick(); + + expect(expandDiscussion).toHaveBeenCalledWith({ discussionId: discussion.id }); + }); + it('does not render jump to thread button', () => { expect(wrapper.find('*[data-original-title="Jump to next unresolved thread"]').exists()).toBe( false, diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb index 888806c4a45..1ad7c7bb9ff 100644 --- a/spec/helpers/gitlab_routing_helper_spec.rb +++ b/spec/helpers/gitlab_routing_helper_spec.rb @@ -224,21 +224,10 @@ RSpec.describe GitlabRoutingHelper do subject { gitlab_raw_snippet_blob_url(snippet, blob.path, ref, args) } - context 'for a PersonalSnippet' do - let(:snippet) { personal_snippet } - - it { expect(subject).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}") } - end - - context 'for a ProjectSnippet' do - let(:snippet) { project_snippet } - - it { expect(subject).to eq("http://test.host/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}") } - end + it_behaves_like 'snippet blob raw url' context 'when an argument is set' do let(:args) { { inline: true } } - let(:snippet) { personal_snippet } it { expect(subject).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}?inline=true") } diff --git a/spec/lib/banzai/filter/inline_alert_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_alert_metrics_filter_spec.rb new file mode 100644 index 00000000000..be40195f001 --- /dev/null +++ b/spec/lib/banzai/filter/inline_alert_metrics_filter_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Banzai::Filter::InlineAlertMetricsFilter do + include FilterSpecHelper + + let(:params) { ['foo', 'bar', 12] } + let(:query_params) { {} } + + let(:trigger_url) { urls.metrics_dashboard_namespace_project_prometheus_alert_url(*params, query_params) } + let(:dashboard_url) { urls.metrics_dashboard_namespace_project_prometheus_alert_url(*params, **query_params, embedded: true, format: :json) } + + it_behaves_like 'a metrics embed filter' + + context 'with query params specified' do + let(:query_params) { { timestamp: 'yesterday' } } + + it_behaves_like 'a metrics embed filter' + end +end diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb index cafcaef8ae2..5f66844f498 100644 --- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb @@ -74,5 +74,20 @@ RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do end end end + + context 'for an alert embed' do + let_it_be(:alert) { create(:prometheus_alert, project: project) } + let(:url) do + urls.metrics_dashboard_project_prometheus_alert_url( + project, + alert.prometheus_metric_id, + environment_id: alert.environment_id, + embedded: true + ) + end + + it_behaves_like 'redacts the embed placeholder' + it_behaves_like 'retains the embed placeholder when applicable' + end end end diff --git a/spec/lib/gitlab/metrics/dashboard/url_spec.rb b/spec/lib/gitlab/metrics/dashboard/url_spec.rb index 56556423b05..205e1000376 100644 --- a/spec/lib/gitlab/metrics/dashboard/url_spec.rb +++ b/spec/lib/gitlab/metrics/dashboard/url_spec.rb @@ -102,6 +102,34 @@ RSpec.describe Gitlab::Metrics::Dashboard::Url do it_behaves_like 'regex which matches url when expected' end + describe '#alert_regex' do + let(:url) do + Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_prometheus_alert_url( + 'foo', + 'bar', + '1', + start: '2020-02-10T12:59:49.938Z', + end: '2020-02-10T20:59:49.938Z', + anchor: "anchor" + ) + end + + let(:expected_params) do + { + 'url' => url, + 'namespace' => 'foo', + 'project' => 'bar', + 'alert' => '1', + 'query' => "?end=2020-02-10T20%3A59%3A49.938Z&start=2020-02-10T12%3A59%3A49.938Z", + 'anchor' => '#anchor' + } + end + + subject { described_class.alert_regex } + + it_behaves_like 'regex which matches url when expected' + end + describe '#build_dashboard_url' do it 'builds the url for the dashboard endpoint' do url = described_class.build_dashboard_url('foo', 'bar', 1) diff --git a/spec/presenters/snippet_blob_presenter_spec.rb b/spec/presenters/snippet_blob_presenter_spec.rb index 00bdb982147..915f43fe572 100644 --- a/spec/presenters/snippet_blob_presenter_spec.rb +++ b/spec/presenters/snippet_blob_presenter_spec.rb @@ -13,13 +13,14 @@ RSpec.describe SnippetBlobPresenter do subject { described_class.new(snippet.blob).rich_data } context 'with PersonalSnippet' do - let(:raw_url) { "http://127.0.0.1:3000/-/snippets/#{snippet.id}/raw" } - let(:snippet) { build(:personal_snippet) } + let(:snippet) { create(:personal_snippet, :repository) } - it 'returns nil when the snippet blob is binary' do - allow(snippet.blob).to receive(:binary?).and_return(true) + context 'when blob is binary' do + it 'returns the HTML associated with the binary' do + allow(snippet).to receive(:blob).and_return(snippet.repository.blob_at('master', 'files/images/logo-black.png')) - expect(subject).to be_nil + expect(subject).to include('file-content image_file') + end end context 'with markdown format' do @@ -108,7 +109,7 @@ RSpec.describe SnippetBlobPresenter do end end - describe '#raw_path' do + describe 'route helpers' do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } let_it_be(:personal_snippet) { create(:personal_snippet, :repository, author: user) } @@ -118,28 +119,62 @@ RSpec.describe SnippetBlobPresenter do project.add_developer(user) end - subject { described_class.new(snippet.blobs.first, current_user: user).raw_path } + describe '#raw_path' do + subject { described_class.new(snippet.blobs.first, current_user: user).raw_path } + + it_behaves_like 'snippet blob raw path' + + context 'with snippet_multiple_files feature disabled' do + before do + stub_feature_flags(snippet_multiple_files: false) + end + + context 'with ProjectSnippet' do + let(:snippet) { project_snippet } + + it 'returns the raw path' do + expect(subject).to eq "/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw" + end + end + + context 'with PersonalSnippet' do + let(:snippet) { personal_snippet } - it_behaves_like 'snippet blob raw path' + it 'returns the raw path' do + expect(subject).to eq "/-/snippets/#{snippet.id}/raw" + end + end + end + end + + describe '#raw_url' do + subject { described_class.new(snippet.blobs.first, current_user: user).raw_url } - context 'with snippet_multiple_files feature disabled' do before do - stub_feature_flags(snippet_multiple_files: false) + stub_default_url_options(host: 'test.host') end - context 'with ProjectSnippet' do - let(:snippet) { project_snippet } + it_behaves_like 'snippet blob raw url' - it 'returns the raw path' do - expect(subject).to eq "/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw" + context 'with snippet_multiple_files feature disabled' do + before do + stub_feature_flags(snippet_multiple_files: false) + end + + context 'with ProjectSnippet' do + let(:snippet) { project_snippet } + + it 'returns the raw project snippet url' do + expect(subject).to eq("http://test.host/#{project_snippet.project.full_path}/-/snippets/#{project_snippet.id}/raw") + end end - end - context 'with PersonalSnippet' do - let(:snippet) { personal_snippet } + context 'with PersonalSnippet' do + let(:snippet) { personal_snippet } - it 'returns the raw path' do - expect(subject).to eq "/-/snippets/#{snippet.id}/raw" + it 'returns the raw personal snippet url' do + expect(subject).to eq("http://test.host/-/snippets/#{personal_snippet.id}/raw") + end end end end diff --git a/spec/requests/api/conan_packages_spec.rb b/spec/requests/api/conan_packages_spec.rb index ac6a4171751..95798b060f1 100644 --- a/spec/requests/api/conan_packages_spec.rb +++ b/spec/requests/api/conan_packages_spec.rb @@ -331,6 +331,18 @@ RSpec.describe API::ConanPackages do .and_return(presenter) end + shared_examples 'rejects invalid upload_url params' do + context 'with unaccepted json format' do + let(:params) { %w[foo bar] } + + it 'returns 400' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end + describe 'GET /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do let(:recipe_path) { package.conan_recipe_path } @@ -418,13 +430,14 @@ RSpec.describe API::ConanPackages do let(:recipe_path) { package.conan_recipe_path } let(:params) do - { "conanfile.py": 24, - "conanmanifext.txt": 123 } + { 'conanfile.py': 24, + 'conanmanifest.txt': 123 } end - subject { post api("/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params, headers: headers } + subject { post api("/packages/conan/v1/conans/#{recipe_path}/upload_urls"), params: params.to_json, headers: headers } it_behaves_like 'rejects invalid recipe' + it_behaves_like 'rejects invalid upload_url params' it 'returns a set of upload urls for the files requested' do subject @@ -436,20 +449,58 @@ RSpec.describe API::ConanPackages do expect(response.body).to eq(expected_response.to_json) end + + context 'with conan_sources and conan_export files' do + let(:params) do + { 'conan_sources.tgz': 345, + 'conan_export.tgz': 234, + 'conanmanifest.txt': 123 } + end + + it 'returns upload urls for the additional files' do + subject + + expected_response = { + 'conan_sources.tgz': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conan_sources.tgz", + 'conan_export.tgz': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conan_export.tgz", + 'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt" + } + + expect(response.body).to eq(expected_response.to_json) + end + end + + context 'with an invalid file' do + let(:params) do + { 'invalid_file.txt': 10, + 'conanmanifest.txt': 123 } + end + + it 'does not return the invalid file as an upload_url' do + subject + + expected_response = { + 'conanmanifest.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt" + } + + expect(response.body).to eq(expected_response.to_json) + end + end end describe 'POST /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel/packages/:conan_package_reference/upload_urls' do let(:recipe_path) { package.conan_recipe_path } let(:params) do - { "conaninfo.txt": 24, - "conanmanifext.txt": 123, - "conan_package.tgz": 523 } + { 'conaninfo.txt': 24, + 'conanmanifest.txt': 123, + 'conan_package.tgz': 523 } end - subject { post api("/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params, headers: headers } + subject { post api("/packages/conan/v1/conans/#{recipe_path}/packages/123456789/upload_urls"), params: params.to_json, headers: headers } it_behaves_like 'rejects invalid recipe' + it_behaves_like 'rejects invalid upload_url params' it 'returns a set of upload urls for the files requested' do expected_response = { @@ -462,6 +513,23 @@ RSpec.describe API::ConanPackages do expect(response.body).to eq(expected_response.to_json) end + + context 'with invalid files' do + let(:params) do + { 'conaninfo.txt': 24, + 'invalid_file.txt': 10 } + end + + it 'returns upload urls only for the valid requested files' do + expected_response = { + 'conaninfo.txt': "#{Settings.gitlab.base_url}/api/v4/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt" + } + + subject + + expect(response.body).to eq(expected_response.to_json) + end + end end describe 'DELETE /api/v4/packages/conan/v1/conans/:package_name/package_version/:package_username/:package_channel' do diff --git a/spec/support/shared_examples/snippet_blob_shared_examples.rb b/spec/support/shared_examples/snippet_blob_shared_examples.rb index ba97688d017..3ed777ee4b8 100644 --- a/spec/support/shared_examples/snippet_blob_shared_examples.rb +++ b/spec/support/shared_examples/snippet_blob_shared_examples.rb @@ -22,3 +22,24 @@ RSpec.shared_examples 'snippet blob raw path' do end end end + +RSpec.shared_examples 'snippet blob raw url' do + let(:blob) { snippet.blobs.first } + let(:ref) { blob.repository.root_ref } + + context 'for PersonalSnippets' do + let(:snippet) { personal_snippet } + + it 'returns the raw personal snippet blob url' do + expect(subject).to eq("http://test.host/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}") + end + end + + context 'for ProjectSnippets' do + let(:snippet) { project_snippet } + + it 'returns the raw project snippet blob url' do + expect(subject).to eq("http://test.host/#{snippet.project.full_path}/-/snippets/#{snippet.id}/raw/#{ref}/#{blob.path}") + end + end +end diff --git a/spec/views/notify/changed_milestone_email.html.haml_spec.rb b/spec/views/notify/changed_milestone_email.html.haml_spec.rb index 50a06683409..03904ff0747 100644 --- a/spec/views/notify/changed_milestone_email.html.haml_spec.rb +++ b/spec/views/notify/changed_milestone_email.html.haml_spec.rb @@ -22,7 +22,7 @@ RSpec.describe 'notify/changed_milestone_email.html.haml' do context 'milestone with start and due dates' do before do - milestone.update(start_date: '2018-01-01', due_date: '2018-12-31') + milestone.update!(start_date: '2018-01-01', due_date: '2018-12-31') end it 'renders with date range' do diff --git a/spec/views/projects/imports/new.html.haml_spec.rb b/spec/views/projects/imports/new.html.haml_spec.rb index edf9eadf924..7c171ee65b9 100644 --- a/spec/views/projects/imports/new.html.haml_spec.rb +++ b/spec/views/projects/imports/new.html.haml_spec.rb @@ -9,7 +9,7 @@ RSpec.describe "projects/imports/new.html.haml" do let(:project) { create(:project_empty_repo, :import_failed, import_type: :gitlab_project, import_source: '/var/opt/gitlab/gitlab-rails/shared/tmp/project_exports/uploads/t.tar.gz', import_url: nil) } before do - project.import_state.update(last_error: '<a href="http://googl.com">Foo</a>') + project.import_state.update!(last_error: '<a href="http://googl.com">Foo</a>') sign_in(user) project.add_maintainer(user) end diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb index 32819fc2cb0..1acc07dabb6 100644 --- a/spec/views/projects/merge_requests/show.html.haml_spec.rb +++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'projects/merge_requests/show.html.haml' do describe 'merge request assignee sidebar' do context 'when assignee is allowed to merge' do it 'does not show a warning icon' do - closed_merge_request.update(assignee_id: user.id) + closed_merge_request.update!(assignee_id: user.id) project.add_maintainer(user) assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, closed_merge_request)) @@ -42,20 +42,4 @@ RSpec.describe 'projects/merge_requests/show.html.haml' do expect(rendered).to have_css('a', visible: false, text: 'Close') end end - - context 'when the merge request is open' do - it 'closes the merge request if the source project does not exist' do - closed_merge_request.update(state: 'open') - forked_project.destroy - # Reload merge request so MergeRequest#source_project turns to `nil` - closed_merge_request.reload - preload_view_requirements - - render - - expect(closed_merge_request.reload.state).to eq('closed') - expect(rendered).to have_css('a', visible: false, text: 'Reopen') - expect(rendered).to have_css('a', visible: false, text: 'Close') - end - end end diff --git a/spec/views/shared/_label_row.html.haml_spec.rb b/spec/views/shared/_label_row.html.haml_spec.rb index 1e2ed41bafc..8f8aa3072e2 100644 --- a/spec/views/shared/_label_row.html.haml_spec.rb +++ b/spec/views/shared/_label_row.html.haml_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'shared/_label_row.html.haml' do label_types.each do |label_type, label_factory| let!(:label) do - label_record = create(label_factory) + label_record = create(label_factory) # rubocop: disable Rails/SaveBang label_record.present(issuable_subject: label_record.subject) end |