diff options
55 files changed, 688 insertions, 300 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 2c5169dc921..79e4d4925f1 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -13,6 +13,7 @@ /doc/development/ @marcia @mjang1 /doc/development/documentation/ @mikelewis /doc/ci @marcel.amirault @sselhorn +/doc/operations @aqualls @eread /doc/user/clusters @aqualls /doc/user/infrastructure @aqualls /doc/user/project/clusters @aqualls @@ -244,7 +244,9 @@ gem 'slack-messenger', '~> 2.3.3' gem 'hangouts-chat', '~> 0.0.5' # Asana integration -gem 'asana', '~> 0.9' +# asana 0.10.1 needs faraday 1.0 +# https://gitlab.com/gitlab-org/gitlab/-/issues/224296 +gem 'asana', '0.10.0' # FogBugz integration gem 'ruby-fogbugz', '~> 0.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index f68fd455352..03c213333df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,7 +76,7 @@ GEM apollo_upload_server (2.0.0.beta.3) graphql (>= 1.8) rails (>= 4.2) - asana (0.9.3) + asana (0.10.0) faraday (~> 0.9) faraday_middleware (~> 0.9) faraday_middleware-multi_json (~> 0.0) @@ -304,7 +304,7 @@ GEM multipart-post (>= 1.2, < 3) faraday-http-cache (2.0.0) faraday (~> 0.8) - faraday_middleware (0.12.2) + faraday_middleware (0.14.0) faraday (>= 0.7.4, < 1.0) faraday_middleware-aws-signers-v4 (0.1.7) aws-sdk-resources (~> 2) @@ -1175,7 +1175,7 @@ DEPENDENCIES addressable (~> 2.7) akismet (~> 3.0) apollo_upload_server (~> 2.0.0.beta3) - asana (~> 0.9) + asana (= 0.10.0) asciidoctor (~> 2.0.10) asciidoctor-include-ext (~> 0.3.1) asciidoctor-plantuml (~> 0.0.12) diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index d74e0887c9b..cac04faae98 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -375,7 +375,7 @@ export const fetchDashboardValidationWarnings = ({ state, dispatch }) => { }) .then(resp => resp.data?.project?.environments?.nodes?.[0]?.metricsDashboard) .then(({ schemaValidationWarnings } = {}) => { - const hasWarnings = schemaValidationWarnings?.length !== 0; + const hasWarnings = schemaValidationWarnings && schemaValidationWarnings.length !== 0; /** * The payload of the dispatch is a boolean, because at the moment a standard * warning message is shown instead of the warnings the BE returns diff --git a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue index 580cca49b5e..a7b7d597fb7 100644 --- a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue +++ b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue @@ -55,13 +55,22 @@ export default { <template> <div class="d-inline-block"> - <button v-gl-modal="modalId" type="button" class="btn btn-danger">{{ __('Delete') }}</button> + <button + v-gl-modal="modalId" + type="button" + class="btn btn-danger" + data-qa-selector="delete_button" + > + {{ __('Delete') }} + </button> <gl-modal :title="title" - :ok-title="s__('WikiPageConfirmDelete|Delete page')" + :action-primary="{ + text: s__('WikiPageConfirmDelete|Delete page'), + attributes: { variant: 'danger', 'data-qa-selector': 'confirm_deletion_button' }, + }" :modal-id="modalId" title-tag="h4" - ok-variant="danger" @ok="onSubmit" > {{ message }} diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb index 3b8f9722913..048b18c5c61 100644 --- a/app/controllers/concerns/snippets_actions.rb +++ b/app/controllers/concerns/snippets_actions.rb @@ -8,6 +8,7 @@ module SnippetsActions include PaginatedCollection include Gitlab::NoteableMetadata include Snippets::SendBlob + include SnippetsSort included do skip_before_action :verify_authenticity_token, diff --git a/app/controllers/concerns/snippets_sort.rb b/app/controllers/concerns/snippets_sort.rb new file mode 100644 index 00000000000..f122c843af7 --- /dev/null +++ b/app/controllers/concerns/snippets_sort.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module SnippetsSort + extend ActiveSupport::Concern + + def sort_param + params[:sort].presence || 'updated_desc' + end +end diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb index aa09fcdbe61..a8ca3dbd0e7 100644 --- a/app/controllers/dashboard/snippets_controller.rb +++ b/app/controllers/dashboard/snippets_controller.rb @@ -3,6 +3,7 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController include PaginatedCollection include Gitlab::NoteableMetadata + include SnippetsSort skip_cross_project_access_check :index @@ -11,7 +12,7 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController .new(current_user, author: current_user) .execute - @snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope]) + @snippets = SnippetsFinder.new(current_user, author: current_user, scope: params[:scope], sort: sort_param) .execute .page(params[:page]) .inc_author diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 1bc63fce5fc..49840e847f2 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -19,7 +19,7 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController .new(current_user, project: @project) .execute - @snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope]) + @snippets = SnippetsFinder.new(current_user, project: @project, scope: params[:scope], sort: sort_param) .execute .page(params[:page]) .inc_author diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 87d87390e57..e68b821459d 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -21,7 +21,7 @@ class SnippetsController < Snippets::ApplicationController if params[:username].present? @user = UserFinder.new(params[:username]).find_by_username! - @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope]) + @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope], sort: sort_param) .execute .page(params[:page]) .inc_author diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 4f63810423b..ecde2c9f475 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -43,7 +43,7 @@ class SnippetsFinder < UnionFinder include Gitlab::Utils::StrongMemoize attr_accessor :current_user, :params - delegate :explore, :only_personal, :only_project, :scope, to: :params + delegate :explore, :only_personal, :only_project, :scope, :sort, to: :params def initialize(current_user = nil, params = {}) @current_user = current_user @@ -69,7 +69,9 @@ class SnippetsFinder < UnionFinder items = init_collection items = by_ids(items) - items.with_optional_visibility(visibility_from_scope).fresh + items = items.with_optional_visibility(visibility_from_scope) + + items.order_by(sort_param) end private @@ -202,6 +204,10 @@ class SnippetsFinder < UnionFinder params[:project].is_a?(Project) ? params[:project] : Project.find_by_id(params[:project]) end end + + def sort_param + sort.presence || 'id_desc' + end end SnippetsFinder.prepend_if_ee('EE::SnippetsFinder') diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb index e2aca2a37d5..e5ac228b0ee 100644 --- a/app/policies/merge_request_policy.rb +++ b/app/policies/merge_request_policy.rb @@ -10,6 +10,10 @@ class MergeRequestPolicy < IssuablePolicy # it would not be safe to prevent :create_note there, since # note permissions are shared, and this would apply too broadly. rule { ~can?(:read_merge_request) }.prevent :create_note + + rule { can?(:update_merge_request) }.policy do + enable :approve_merge_request + end end MergeRequestPolicy.prepend_if_ee('EE::MergeRequestPolicy') diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index 6e89cadb82d..d0e81da5aab 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -2,8 +2,6 @@ module Members class DestroyService < Members::BaseService - WAIT_FOR_DELETE = 1.hour - def execute(member, skip_authorization: false, skip_subresources: false, unassign_issuables: false) raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_destroy_member?(member) @@ -72,7 +70,7 @@ module Members source_type = member.is_a?(GroupMember) ? 'Group' : 'Project' member.run_after_commit do - MembersDestroyer::UnassignIssuablesWorker.perform_in(WAIT_FOR_DELETE, member.user_id, member.source_id, source_type) + MembersDestroyer::UnassignIssuablesWorker.perform_async(member.user_id, member.source_id, source_type) end end end diff --git a/app/services/merge_requests/approval_service.rb b/app/services/merge_requests/approval_service.rb index 0fe165303f2..150ec85fca9 100644 --- a/app/services/merge_requests/approval_service.rb +++ b/app/services/merge_requests/approval_service.rb @@ -3,19 +3,27 @@ module MergeRequests class ApprovalService < MergeRequests::BaseService def execute(merge_request) + return unless can_be_approved?(merge_request) + approval = merge_request.approvals.new(user: current_user) - return unless save_approval(approval) + return success unless save_approval(approval) reset_approvals_cache(merge_request) create_event(merge_request) create_approval_note(merge_request) mark_pending_todos_as_done(merge_request) execute_approval_hooks(merge_request, current_user) + + success end private + def can_be_approved?(merge_request) + current_user.can?(:approve_merge_request, merge_request) + end + def reset_approvals_cache(merge_request) merge_request.approvals.reset end diff --git a/app/services/merge_requests/remove_approval_service.rb b/app/services/merge_requests/remove_approval_service.rb index e2675f64ee4..aeaaebdbff6 100644 --- a/app/services/merge_requests/remove_approval_service.rb +++ b/app/services/merge_requests/remove_approval_service.rb @@ -4,6 +4,8 @@ module MergeRequests class RemoveApprovalService < MergeRequests::BaseService # rubocop: disable CodeReuse/ActiveRecord def execute(merge_request) + return unless approved_by_user?(merge_request) + # paranoid protection against running wrong deletes return unless merge_request.id && current_user.id @@ -15,11 +17,17 @@ module MergeRequests reset_approvals_cache(merge_request) create_note(merge_request) end + + success end # rubocop: enable CodeReuse/ActiveRecord private + def approved_by_user?(merge_request) + merge_request.approved_by_users.include?(current_user) + end + def reset_approvals_cache(merge_request) merge_request.approvals.reset end diff --git a/app/views/notify/push_to_merge_request_email.text.haml b/app/views/notify/push_to_merge_request_email.text.haml index 55cbd62b7e8..5c2005a47e5 100644 --- a/app/views/notify/push_to_merge_request_email.text.haml +++ b/app/views/notify/push_to_merge_request_email.text.haml @@ -1,4 +1,6 @@ -#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{merge_request_reference_link(@merge_request)} +#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{@merge_request.to_reference} + +Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @merge_request)} \ - if @existing_commits.any? - count = @existing_commits.size diff --git a/changelogs/unreleased/217362-move-plan-stage-usage-activity-to-ce.yml b/changelogs/unreleased/217362-move-plan-stage-usage-activity-to-ce.yml new file mode 100644 index 00000000000..6bfffbc19a3 --- /dev/null +++ b/changelogs/unreleased/217362-move-plan-stage-usage-activity-to-ce.yml @@ -0,0 +1,5 @@ +--- +title: Move plan stage usage activity to CE +merge_request: 36087 +author: +type: changed diff --git a/changelogs/unreleased/223159-fix-dahsboard-warning-logic.yml b/changelogs/unreleased/223159-fix-dahsboard-warning-logic.yml new file mode 100644 index 00000000000..ba69c9ac07d --- /dev/null +++ b/changelogs/unreleased/223159-fix-dahsboard-warning-logic.yml @@ -0,0 +1,5 @@ +--- +title: Fix dashboard schema validation issue +merge_request: 36577 +author: +type: fixed diff --git a/changelogs/unreleased/fj-default-order-snippet-lists.yml b/changelogs/unreleased/fj-default-order-snippet-lists.yml new file mode 100644 index 00000000000..e902628ee0f --- /dev/null +++ b/changelogs/unreleased/fj-default-order-snippet-lists.yml @@ -0,0 +1,5 @@ +--- +title: Reorder snippets in lists using `updated_at` column +merge_request: 34393 +author: Dibyadarshi Dash @ddash2 +type: changed diff --git a/changelogs/unreleased/propagate-ds-java-version-in-dependency-scanning.yml b/changelogs/unreleased/propagate-ds-java-version-in-dependency-scanning.yml new file mode 100644 index 00000000000..85677505b0b --- /dev/null +++ b/changelogs/unreleased/propagate-ds-java-version-in-dependency-scanning.yml @@ -0,0 +1,5 @@ +--- +title: Propagate DS_JAVA_VERSION for dependency scanning +merge_request: 36448 +author: +type: fixed diff --git a/changelogs/unreleased/tc-fix-plain-text-commit-mails.yml b/changelogs/unreleased/tc-fix-plain-text-commit-mails.yml new file mode 100644 index 00000000000..a3475d4529f --- /dev/null +++ b/changelogs/unreleased/tc-fix-plain-text-commit-mails.yml @@ -0,0 +1,5 @@ +--- +title: Remove HTML link from plain text mail +merge_request: 36301 +author: +type: fixed diff --git a/doc/README.md b/doc/README.md index d40741e0174..8e7934172c0 100644 --- a/doc/README.md +++ b/doc/README.md @@ -138,7 +138,7 @@ The following documentation relates to the DevOps **Plan** stage: Consolidate source code into a single [distributed version control system](https://en.wikipedia.org/wiki/Distributed_version_control) that’s easily managed and controlled without disrupting your workflow. -GitLab’s Git repositories come complete with branching tools and access +GitLab repositories come complete with branching tools and access controls, providing a scalable, single source of truth for collaborating on projects and code. diff --git a/doc/administration/feature_flags.md b/doc/administration/feature_flags.md index 5aadbf75c5d..678ab6c5d7b 100644 --- a/doc/administration/feature_flags.md +++ b/doc/administration/feature_flags.md @@ -109,6 +109,24 @@ For example, to enable the [`:junit_pipeline_view`](../ci/junit_test_reports.md# Feature.enable(:junit_pipeline_view, Project.find(1234)) ``` +`Feature.enable` and `Feature.disable` always return `nil`, this is not an indication that the command failed: + +```ruby +irb(main):001:0> Feature.enable(:release_evidence_collection) +=> nil +``` + +To check if a flag is enabled or disabled you can use `Feature.enabled?` or `Feature.disabled?`: + +```ruby +Feature.enable(:release_evidence_collection) +=> nil +Feature.enabled?(:release_evidence_collection) +=> true +Feature.disabled?(:release_evidence_collection) +=> false +``` + When the feature is ready, GitLab will remove the feature flag, the option for enabling and disabling it will no longer exist, and the feature will become available in all instances. diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md index 8f55345a9a8..169d02fe13d 100644 --- a/doc/administration/packages/container_registry.md +++ b/doc/administration/packages/container_registry.md @@ -927,7 +927,7 @@ larger images, or images that take longer than 5 minutes to push, users may encounter this error. Administrators can increase the token duration in **Admin area > Settings > -Container Registry > Authorization token duration (minutes)**. +CI/CD > Container Registry > Authorization token duration (minutes)**. ### AWS S3 with the GitLab registry error when pushing large images diff --git a/doc/development/fe_guide/tooling.md b/doc/development/fe_guide/tooling.md index 585cd969c96..c693df36e6e 100644 --- a/doc/development/fe_guide/tooling.md +++ b/doc/development/fe_guide/tooling.md @@ -4,6 +4,44 @@ We use ESLint to encapsulate and enforce frontend code standards. Our configuration may be found in the [`gitlab-eslint-config`](https://gitlab.com/gitlab-org/gitlab-eslint-config) project. +### Yarn Script + +This section describes yarn scripts that are available to validate and apply automatic fixes to files using ESLint. + +To check all currently staged files (based on `git diff`) with ESLint, run the following script: + +```shell +yarn eslint-staged +``` + +_A list of problems found will be logged to the console._ + +To apply automatic ESLint fixes to all currently staged files (based on `git diff`), run the following script: + +```shell +yarn eslint-staged-fix +``` + +_If manual changes are required, a list of changes will be sent to the console._ + +To check **all** files in the repository with ESLint, run the following script: + +```shell +yarn eslint +``` + +_A list of problems found will be logged to the console._ + +To apply automatic ESLint fixes to **all** files in the repository, run the following script: + +```shell +yarn eslint-fix +``` + +_If manual changes are required, a list of changes will be sent to the console._ + +**Caution:** Limit use to global rule updates. Otherwise, the changes can lead to huge Merge Requests. + ### Disabling ESLint in new files Do not disable ESLint when creating new files. Existing files may have existing rules diff --git a/doc/development/telemetry/usage_ping.md b/doc/development/telemetry/usage_ping.md index 0ca1018352c..ee3bf644a23 100644 --- a/doc/development/telemetry/usage_ping.md +++ b/doc/development/telemetry/usage_ping.md @@ -681,10 +681,19 @@ appear to be associated to any of the services running, since they all appear to | `ldap_group_sync_enabled` | `usage_activity_by_stage` | `manage` | | EE | | | `ldap_admin_sync_enabled` | `usage_activity_by_stage` | `manage` | | EE | | | `group_saml_enabled` | `usage_activity_by_stage` | `manage` | | EE | | -| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | | | -| `service_desk_enabled_projects` | `usage_activity_by_stage` | `plan` | | | | -| `service_desk_issues` | `usage_activity_by_stage` | `plan` | | | | -| `todos: 0` | `usage_activity_by_stage` | `plan` | | | | +| `issues` | `usage_activity_by_stage` | `plan` | | CE+EE | | +| `notes` | `usage_activity_by_stage` | `plan` | | CE+EE | | +| `projects` | `usage_activity_by_stage` | `plan` | | CE+EE | | +| `todos` | `usage_activity_by_stage` | `plan` | | CE+EE | | +| `assignee_lists` | `usage_activity_by_stage` | `plan` | | EE | | +| `epics` | `usage_activity_by_stage` | `plan` | | EE | | +| `label_lists` | `usage_activity_by_stage` | `plan` | | EE | | +| `milestone_lists` | `usage_activity_by_stage` | `plan` | | EE | | +| `projects_jira_active` | `usage_activity_by_stage` | `plan` | | EE | | +| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | EE | | +| `projects_jira_dvcs_server_active` | `usage_activity_by_stage` | `plan` | | EE | | +| `service_desk_enabled_projects` | `usage_activity_by_stage` | `plan` | | EE | | +| `service_desk_issues` | `usage_activity_by_stage` | `plan` | | EE | | | `deployments` | `usage_activity_by_stage` | `release` | | CE+EE | Total deployments | | `failed_deployments` | `usage_activity_by_stage` | `release` | | CE+EE | Total failed deployments | | `projects_mirrored_with_pipelines_enabled` | `usage_activity_by_stage` | `release` | | EE | Projects with repository mirroring enabled | diff --git a/doc/operations/index.md b/doc/operations/index.md index f39d664a21b..314a1b231ba 100644 --- a/doc/operations/index.md +++ b/doc/operations/index.md @@ -11,6 +11,7 @@ your applications: - Collect [Prometheus metrics](../user/project/integrations/prometheus_library/index.md). - Deploy to different [environments](../ci/environments/index.md). +- Manage your [Alerts](../user/project/operations/alert_management.md) and [Incidents](../user/incident_management/index.md). - Connect your project to a [Kubernetes cluster](../user/project/clusters/index.md). - Manage your infrastructure with [Infrastructure as Code](../user/infrastructure/index.md) approaches. - Discover and view errors generated by your applications with [Error Tracking](../user/project/operations/error_tracking.md). diff --git a/doc/operations/metrics/dashboards/panel_types.md b/doc/operations/metrics/dashboards/panel_types.md new file mode 100644 index 00000000000..2b4f65d0f67 --- /dev/null +++ b/doc/operations/metrics/dashboards/panel_types.md @@ -0,0 +1,261 @@ +--- +stage: Monitor +group: APM +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 +--- + +# Panel types for dashboards + +The below panel types are supported in monitoring dashboards. + +## Area or Line Chart + +To add an area chart panel type to a dashboard, look at the following sample dashboard file: + +```yaml +dashboard: 'Dashboard Title' +panel_groups: + - group: 'Group Title' + panels: + - type: area-chart # or line-chart + title: 'Area Chart Title' + y_label: "Y-Axis" + y_axis: + format: number + precision: 0 + metrics: + - id: area_http_requests_total + query_range: 'http_requests_total' + label: "Instance: {{instance}}, Method: {{method}}" + unit: "count" +``` + +Note the following properties: + +| Property | Type | Required | Description | +| ------ | ------ | ------ | ------ | +| type | string | no | Type of panel to be rendered. Optional for area panel types | +| query_range | string | required | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) | + +![area panel chart](../../../user/project/integrations/img/prometheus_dashboard_area_panel_type_v12_8.png) + +Starting in [version 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/202696), the y-axis values will automatically scale according to the data. Previously, it always started from 0. + +## Anomaly chart + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16530) in GitLab 12.5. + +To add an anomaly chart panel type to a dashboard, add a panel with *exactly* 3 metrics. + +The first metric represents the current state, and the second and third metrics represent the upper and lower limit respectively: + +```yaml +dashboard: 'Dashboard Title' +panel_groups: + - group: 'Group Title' + panels: + - type: anomaly-chart + title: "Chart Title" + y_label: "Y-Axis" + metrics: + - id: anomaly_requests_normal + query_range: 'http_requests_total' + label: "# of Requests" + unit: "count" + metrics: + - id: anomaly_requests_upper_limit + query_range: 10000 + label: "Max # of requests" + unit: "count" + metrics: + - id: anomaly_requests_lower_limit + query_range: 2000 + label: "Min # of requests" + unit: "count" +``` + +Note the following properties: + +| Property | Type | Required | Description | +| ------ | ------ | ------ | ------ | +| type | string | required | Must be `anomaly-chart` for anomaly panel types | +| query_range | yes | required | For anomaly panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) in every metric. | + +![anomaly panel type](../../../user/project/integrations/img/prometheus_dashboard_anomaly_panel_type.png) + +## Bar chart + +To add a bar chart to a dashboard, look at the following sample dashboard file: + +```yaml +dashboard: 'Dashboard Title' +panel_groups: + - group: 'Group title' + panels: + - type: bar + title: "Http Handlers" + x_label: 'Response Size' + y_axis: + name: "Handlers" + metrics: + - id: prometheus_http_response_size_bytes_bucket + query_range: "sum(increase(prometheus_http_response_size_bytes_bucket[1d])) by (handler)" + unit: 'Bytes' +``` + +Note the following properties: + +| Property | Type | Required | Description | +| ------ | ------ | ------ | ------ | +| `type` | string | yes | Type of panel to be rendered. For bar chart types, set to `bar` | +| `query_range` | yes | yes | For bar chart, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) + +![bar chart panel type](../../../user/project/integrations/img/prometheus_dashboard_bar_chart_panel_type_v12.10.png) + +## Column chart + +To add a column panel type to a dashboard, look at the following sample dashboard file: + +```yaml +dashboard: 'Dashboard Title' +panel_groups: + - group: 'Group title' + panels: + - title: "Column" + type: "column" + metrics: + - id: 1024_memory + query: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024' + unit: MB + label: "Memory Usage" +``` + +Note the following properties: + +| Property | Type | Required | Description | +| ------ | ------ | ------ | ------ | +| type | string | yes | Type of panel to be rendered. For column panel types, set to `column` | +| query_range | yes | yes | For column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) | + +![anomaly panel type](../../../user/project/integrations/img/prometheus_dashboard_column_panel_type.png) + +## Stacked column + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30583) in GitLab 12.8. + +To add a stacked column panel type to a dashboard, look at the following sample dashboard file: + +```yaml +dashboard: 'Dashboard title' +priority: 1 +panel_groups: + - group: 'Group Title' + priority: 5 + panels: + - type: 'stacked-column' + title: "Stacked column" + y_label: "y label" + x_label: 'x label' + metrics: + - id: memory_1 + query_range: 'memory_query' + label: "memory query 1" + unit: "count" + series_name: 'group 1' + - id: memory_2 + query_range: 'memory_query_2' + label: "memory query 2" + unit: "count" + series_name: 'group 2' +``` + +![stacked column panel type](../../../user/project/integrations/img/prometheus_dashboard_stacked_column_panel_type_v12_8.png) + +| Property | Type | Required | Description | +| ------ | ------ | ------ | ------ | +| `type` | string | yes | Type of panel to be rendered. For stacked column panel types, set to `stacked-column` | +| `query_range` | yes | yes | For stacked column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) | + +## Single Stat + +To add a single stat panel type to a dashboard, look at the following sample dashboard file: + +```yaml +dashboard: 'Dashboard Title' +panel_groups: + - group: 'Group Title' + panels: + - title: "Single Stat" + type: "single-stat" + metrics: + - id: 10 + query: 'max(go_memstats_alloc_bytes{job="prometheus"})' + unit: MB + label: "Total" +``` + +Note the following properties: + +| Property | Type | Required | Description | +| ------ | ------ | ------ | ------ | +| type | string | yes | Type of panel to be rendered. For single stat panel types, set to `single-stat` | +| query | string | yes | For single stat panel types, you must use an [instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) | + +![single stat panel type](../../../user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png) + +## Percentile based results + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201946) in GitLab 12.8. + +Query results sometimes need to be represented as a percentage value out of 100. You can use the `max_value` property at the root of the panel definition: + +```yaml +dashboard: 'Dashboard Title' +panel_groups: + - group: 'Group Title' + panels: + - title: "Single Stat" + type: "single-stat" + max_value: 100 + metrics: + - id: 10 + query: 'max(go_memstats_alloc_bytes{job="prometheus"})' + unit: '%' + label: "Total" +``` + +For example, if you have a query value of `53.6`, adding `%` as the unit results in a single stat value of `53.6%`, but if the maximum expected value of the query is `120`, the value would be `44.6%`. Adding the `max_value` causes the correct percentage value to display. + +## Heatmaps + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30581) in GitLab 12.5. + +To add a heatmap panel type to a dashboard, look at the following sample dashboard file: + +```yaml +dashboard: 'Dashboard Title' +panel_groups: + - group: 'Group Title' + panels: + - title: "Heatmap" + type: "heatmap" + metrics: + - id: 10 + query: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)' + unit: req/sec + label: "Status code" +``` + +Note the following properties: + +| Property | Type | Required | Description | +| ------ | ------ | ------ | ------ | +| type | string | yes | Type of panel to be rendered. For heatmap panel types, set to `heatmap` | +| query_range | yes | yes | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) | + +![heatmap panel type](../../../user/project/integrations/img/heatmap_panel_type.png) + +CAUTION: **Warning:** +When a query returns too many data points, the heatmap data bucket dimensions tend downwards to 0, making the chart's data invisible, as shown in the image below. To fix this problem, limit the amount of data returned by changing the time range filter on the metrics dashboard UI, or adding the **step** property to your dashboard's YAML file. + +![heatmap chart_too_much_data](../../../user/project/integrations/img/heatmap_chart_too_much_data_v_13_2.png) diff --git a/doc/operations/metrics/dashboards/yaml.md b/doc/operations/metrics/dashboards/yaml.md index ce834fabca7..501bfd29eb5 100644 --- a/doc/operations/metrics/dashboards/yaml.md +++ b/doc/operations/metrics/dashboards/yaml.md @@ -59,7 +59,7 @@ Panels in a panel group are laid out in rows consisting of two panels per row. A | `title` | string | yes | Heading for the panel. | | `y_label` | string | no, but highly encouraged | Y-Axis label for the panel. | | `y_axis` | string | no | Y-Axis configuration for the panel. | -| `max_value` | number | no | Denominator value used for calculating [percentile based results](../../../user/project/integrations/prometheus.md#percentile-based-results) | +| `max_value` | number | no | Denominator value used for calculating [percentile based results](panel_types.md#percentile-based-results) | | `weight` | number | no, defaults to order in file | Order to appear within the grouping. Lower number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. | | `metrics` | array | yes | The metrics which should be displayed in the panel. Any number of metrics can be displayed when `type` is `area-chart` or `line-chart`, whereas only 3 can be displayed when `type` is `anomaly-chart`. | | `links` | array | no | Add links to display on the chart's [context menu](index.md#chart-context-menu). | diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 321487a1c6d..f08a144db90 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -120,6 +120,7 @@ The following table depicts the various user permission levels in a project. | Rewrite/remove Git tags | | | ✓ | ✓ | ✓ | | Manage Feature Flags **(PREMIUM)** | | | ✓ | ✓ | ✓ | | Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ | +| Run CI/CD pipeline against a protected branch | | | ✓ (*5*) | ✓ | ✓ | | Use environment terminals | | | | ✓ | ✓ | | Run Web IDE's Interactive Web Terminals **(ULTIMATE ONLY)** | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 1e09c7f1a0b..e3ed5e3e764 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -353,262 +353,6 @@ When **Metrics Dashboard YAML definition is invalid** at least one of the follow Metrics Dashboard YAML definition validation information is also available as a [GraphQL API field](../../../api/graphql/reference/index.md#metricsdashboard) -#### Panel types for dashboards - -The below panel types are supported in monitoring dashboards. - -##### Area or Line Chart - -To add an area chart panel type to a dashboard, look at the following sample dashboard file: - -```yaml -dashboard: 'Dashboard Title' -panel_groups: - - group: 'Group Title' - panels: - - type: area-chart # or line-chart - title: 'Area Chart Title' - y_label: "Y-Axis" - y_axis: - format: number - precision: 0 - metrics: - - id: area_http_requests_total - query_range: 'http_requests_total' - label: "Instance: {{instance}}, Method: {{method}}" - unit: "count" -``` - -Note the following properties: - -| Property | Type | Required | Description | -| ------ | ------ | ------ | ------ | -| type | string | no | Type of panel to be rendered. Optional for area panel types | -| query_range | string | required | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) | - -![area panel chart](img/prometheus_dashboard_area_panel_type_v12_8.png) - -Starting in [version 12.8](https://gitlab.com/gitlab-org/gitlab/-/issues/202696), the y-axis values will automatically scale according to the data. Previously, it always started from 0. - -##### Anomaly chart - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16530) in GitLab 12.5. - -To add an anomaly chart panel type to a dashboard, add a panel with *exactly* 3 metrics. - -The first metric represents the current state, and the second and third metrics represent the upper and lower limit respectively: - -```yaml -dashboard: 'Dashboard Title' -panel_groups: - - group: 'Group Title' - panels: - - type: anomaly-chart - title: "Chart Title" - y_label: "Y-Axis" - metrics: - - id: anomaly_requests_normal - query_range: 'http_requests_total' - label: "# of Requests" - unit: "count" - metrics: - - id: anomaly_requests_upper_limit - query_range: 10000 - label: "Max # of requests" - unit: "count" - metrics: - - id: anomaly_requests_lower_limit - query_range: 2000 - label: "Min # of requests" - unit: "count" -``` - -Note the following properties: - -| Property | Type | Required | Description | -| ------ | ------ | ------ | ------ | -| type | string | required | Must be `anomaly-chart` for anomaly panel types | -| query_range | yes | required | For anomaly panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) in every metric. | - -![anomaly panel type](img/prometheus_dashboard_anomaly_panel_type.png) - -##### Bar chart - -To add a bar chart to a dashboard, look at the following sample dashboard file: - -```yaml -dashboard: 'Dashboard Title' -panel_groups: - - group: 'Group title' - panels: - - type: bar - title: "Http Handlers" - x_label: 'Response Size' - y_axis: - name: "Handlers" - metrics: - - id: prometheus_http_response_size_bytes_bucket - query_range: "sum(increase(prometheus_http_response_size_bytes_bucket[1d])) by (handler)" - unit: 'Bytes' -``` - -Note the following properties: - -| Property | Type | Required | Description | -| ------ | ------ | ------ | ------ | -| `type` | string | yes | Type of panel to be rendered. For bar chart types, set to `bar` | -| `query_range` | yes | yes | For bar chart, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) - -![bar chart panel type](img/prometheus_dashboard_bar_chart_panel_type_v12.10.png) - -##### Column chart - -To add a column panel type to a dashboard, look at the following sample dashboard file: - -```yaml -dashboard: 'Dashboard Title' -panel_groups: - - group: 'Group title' - panels: - - title: "Column" - type: "column" - metrics: - - id: 1024_memory - query: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024' - unit: MB - label: "Memory Usage" -``` - -Note the following properties: - -| Property | Type | Required | Description | -| ------ | ------ | ------ | ------ | -| type | string | yes | Type of panel to be rendered. For column panel types, set to `column` | -| query_range | yes | yes | For column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) | - -![anomaly panel type](img/prometheus_dashboard_column_panel_type.png) - -##### Stacked column - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30583) in GitLab 12.8. - -To add a stacked column panel type to a dashboard, look at the following sample dashboard file: - -```yaml -dashboard: 'Dashboard title' -priority: 1 -panel_groups: - - group: 'Group Title' - priority: 5 - panels: - - type: 'stacked-column' - title: "Stacked column" - y_label: "y label" - x_label: 'x label' - metrics: - - id: memory_1 - query_range: 'memory_query' - label: "memory query 1" - unit: "count" - series_name: 'group 1' - - id: memory_2 - query_range: 'memory_query_2' - label: "memory query 2" - unit: "count" - series_name: 'group 2' -``` - -![stacked column panel type](img/prometheus_dashboard_stacked_column_panel_type_v12_8.png) - -| Property | Type | Required | Description | -| ------ | ------ | ------ | ------ | -| `type` | string | yes | Type of panel to be rendered. For stacked column panel types, set to `stacked-column` | -| `query_range` | yes | yes | For stacked column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) | - -##### Single Stat - -To add a single stat panel type to a dashboard, look at the following sample dashboard file: - -```yaml -dashboard: 'Dashboard Title' -panel_groups: - - group: 'Group Title' - panels: - - title: "Single Stat" - type: "single-stat" - metrics: - - id: 10 - query: 'max(go_memstats_alloc_bytes{job="prometheus"})' - unit: MB - label: "Total" -``` - -Note the following properties: - -| Property | Type | Required | Description | -| ------ | ------ | ------ | ------ | -| type | string | yes | Type of panel to be rendered. For single stat panel types, set to `single-stat` | -| query | string | yes | For single stat panel types, you must use an [instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) | - -![single stat panel type](img/prometheus_dashboard_single_stat_panel_type.png) - -###### Percentile based results - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201946) in GitLab 12.8. - -Query results sometimes need to be represented as a percentage value out of 100. You can use the `max_value` property at the root of the panel definition: - -```yaml -dashboard: 'Dashboard Title' -panel_groups: - - group: 'Group Title' - panels: - - title: "Single Stat" - type: "single-stat" - max_value: 100 - metrics: - - id: 10 - query: 'max(go_memstats_alloc_bytes{job="prometheus"})' - unit: '%' - label: "Total" -``` - -For example, if you have a query value of `53.6`, adding `%` as the unit results in a single stat value of `53.6%`, but if the maximum expected value of the query is `120`, the value would be `44.6%`. Adding the `max_value` causes the correct percentage value to display. - -##### Heatmaps - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30581) in GitLab 12.5. - -To add a heatmap panel type to a dashboard, look at the following sample dashboard file: - -```yaml -dashboard: 'Dashboard Title' -panel_groups: - - group: 'Group Title' - panels: - - title: "Heatmap" - type: "heatmap" - metrics: - - id: 10 - query: 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)' - unit: req/sec - label: "Status code" -``` - -Note the following properties: - -| Property | Type | Required | Description | -| ------ | ------ | ------ | ------ | -| type | string | yes | Type of panel to be rendered. For heatmap panel types, set to `heatmap` | -| query_range | yes | yes | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) | - -![heatmap panel type](img/heatmap_panel_type.png) - -CAUTION: **Warning:** -When a query returns too many data points, the heatmap data bucket dimensions tend downwards to 0, making the chart's data invisible, as shown in the image below. To fix this problem, limit the amount of data returned by changing the time range filter on the metrics dashboard UI, or adding the **step** property to your dashboard's YAML file. - -![heatmap chart_too_much_data](img/heatmap_chart_too_much_data_v_13_2.png) - ### Templating variables for metrics dashboards > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214539) in GitLab 13.0. diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml index e2aa44e78f4..37f6cd216ca 100644 --- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml @@ -52,6 +52,7 @@ dependency_scanning: DS_PYTHON_VERSION \ DS_PIP_VERSION \ DS_PIP_DEPENDENCY_PATH \ + DS_JAVA_VERSION \ GEMNASIUM_DB_LOCAL_PATH \ GEMNASIUM_DB_REMOTE_URL \ GEMNASIUM_DB_REF_NAME \ diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 3ccbb7eee49..d0f910c3160 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -525,9 +525,16 @@ module Gitlab # Omitted because no user, creator or author associated: `boards`, `labels`, `milestones`, `uploads` # Omitted because too expensive: `epics_deepest_relationship_level` # Omitted because of encrypted properties: `projects_jira_cloud_active`, `projects_jira_server_active` + # rubocop: disable CodeReuse/ActiveRecord def usage_activity_by_stage_plan(time_period) - {} + { + issues: distinct_count(::Issue.where(time_period), :author_id), + notes: distinct_count(::Note.where(time_period), :author_id), + projects: distinct_count(::Project.where(time_period), :creator_id), + todos: distinct_count(::Todo.where(time_period), :author_id) + } end + # rubocop: enable CodeReuse/ActiveRecord # Omitted because no user, creator or author associated: `environments`, `feature_flags`, `in_review_folder`, `pages_domains` # rubocop: disable CodeReuse/ActiveRecord diff --git a/package.json b/package.json index cdfd6e2a7a4..5f2c4cac587 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "dev-server": "NODE_OPTIONS=\"--max-old-space-size=3584\" node scripts/frontend/webpack_dev_server.js", "eslint": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .", "eslint-fix": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .", + "eslint-staged": "git diff --cached --name-only | grep -E \"(.*)\\.(js|vue)$\" | xargs eslint --cache --max-warnings 0 --report-unused-disable-directives", + "eslint-staged-fix": "git diff --cached --name-only | grep -E \"(.*)\\.(js|vue)$\" | xargs eslint --cache --max-warnings 0 --report-unused-disable-directives --fix", "eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .", "file-coverage": "scripts/frontend/file_test_coverage.js", "prejest": "yarn check-dependencies", @@ -41,7 +43,7 @@ "@babel/preset-env": "^7.10.1", "@gitlab/at.js": "1.5.5", "@gitlab/svgs": "1.151.0", - "@gitlab/ui": "17.19.1", + "@gitlab/ui": "17.21.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "^6.0.3-1", "@sentry/browser": "^5.10.2", @@ -474,6 +474,10 @@ module QA autoload :Templates, 'qa/page/component/project/templates' end end + + module Modal + autoload :DeleteWiki, 'qa/page/modal/delete_wiki' + end end ## diff --git a/qa/qa/page/modal/delete_wiki.rb b/qa/qa/page/modal/delete_wiki.rb new file mode 100644 index 00000000000..4f0bc34ee88 --- /dev/null +++ b/qa/qa/page/modal/delete_wiki.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module QA + module Page + module Modal + class DeleteWiki < Base + view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do + element :confirm_deletion_button, required: true + end + + def confirm_deletion + click_element :confirm_deletion_button + end + end + end + end +end diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb index 41ee205429c..6f3be904eb3 100644 --- a/qa/qa/page/project/wiki/edit.rb +++ b/qa/qa/page/project/wiki/edit.rb @@ -15,6 +15,10 @@ module QA element :create_page_button end + view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do + element :delete_button + end + def set_title(title) fill_element :wiki_title_textbox, title end @@ -34,6 +38,11 @@ module QA def click_create_page click_element :create_page_button end + + def delete_page + click_element :delete_button, Page::Modal::DeleteWiki + Page::Modal::DeleteWiki.perform(&:confirm_deletion) + end end end end diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb index 2e68bcaae90..cdd18e420d1 100644 --- a/qa/qa/page/project/wiki/show.rb +++ b/qa/qa/page/project/wiki/show.rb @@ -58,6 +58,10 @@ module QA def has_content?(content) has_element?(:wiki_page_content, content) end + + def has_no_page? + has_element? :create_first_page_link + end end end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb index ba8e8635c87..5b0b4b2970d 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb @@ -22,6 +22,8 @@ module QA Page::Project::Menu.perform(&:click_issues) Page::Project::Issue::Index.perform do |issues_page| + expect(issues_page).to have_content("2 issues successfully imported") + issues_page.click_issue_link(jira_issue_title) end end diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb new file mode 100644 index 00000000000..923c7332748 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + context 'Wiki' do + let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! } + + before do + Flow::Login.sign_in + end + + context 'Page deletion' do + it 'has removed the deleted page correctly' do + initial_wiki.visit! + + Page::Project::Wiki::Show.perform(&:click_edit) + Page::Project::Wiki::Edit.perform(&:delete_page) + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_no_page + end + end + end + end + end +end diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index 4e5896781e7..72e6334d0fc 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -48,7 +48,8 @@ echo if [ ${FIND_READMES} -ne $NUMBER_READMES ] then echo - echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2 + echo ' ✖ ERROR: The number of README.md file(s) has changed. Use index.md instead of README.md.' >&2 + echo ' ✖ If removing a README.md file, update NUMBER_READMES in lint-doc.sh.' >&2 echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#work-with-directories-and-files' echo ((ERRORCODE++)) diff --git a/spec/controllers/concerns/renders_commits_spec.rb b/spec/controllers/concerns/renders_commits_spec.rb index 0bffb39d608..7be5f75c19d 100644 --- a/spec/controllers/concerns/renders_commits_spec.rb +++ b/spec/controllers/concerns/renders_commits_spec.rb @@ -46,7 +46,7 @@ RSpec.describe RendersCommits do it 'avoids N + 1' do stub_const("MergeRequestDiff::COMMITS_SAFE_SIZE", 5) - control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + control_count = ActiveRecord::QueryRecorder.new do go end.count diff --git a/spec/controllers/dashboard/snippets_controller_spec.rb b/spec/controllers/dashboard/snippets_controller_spec.rb index 3c316d07408..d981f738e70 100644 --- a/spec/controllers/dashboard/snippets_controller_spec.rb +++ b/spec/controllers/dashboard/snippets_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Dashboard::SnippetsController do - let(:user) { create(:user) } + let_it_be(:user) { create(:user) } before do sign_in(user) @@ -26,5 +26,7 @@ RSpec.describe Dashboard::SnippetsController do get :index end + + it_behaves_like 'snippets sort order' end end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 8bbfaa8d327..6fcb24da3cd 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -15,14 +15,18 @@ RSpec.describe Projects::SnippetsController do end describe 'GET #index' do + let(:base_params) do + { + namespace_id: project.namespace, + project_id: project + } + end + + subject { get :index, params: base_params } + it_behaves_like 'paginated collection' do let(:collection) { project.snippets } - let(:params) do - { - namespace_id: project.namespace, - project_id: project - } - end + let(:params) { base_params } before do create(:project_snippet, :public, project: project, author: user) @@ -35,7 +39,11 @@ RSpec.describe Projects::SnippetsController do .to receive(:new).with(nil, project: project) .and_return(service) - get :index, params: { namespace_id: project.namespace, project_id: project } + subject + end + + it_behaves_like 'snippets sort order' do + let(:params) { base_params } end context 'when the project snippet is private' do @@ -43,7 +51,7 @@ RSpec.describe Projects::SnippetsController do context 'when anonymous' do it 'does not include the private snippet' do - get :index, params: { namespace_id: project.namespace, project_id: project } + subject expect(assigns(:snippets)).not_to include(project_snippet) expect(response).to have_gitlab_http_status(:ok) @@ -56,7 +64,7 @@ RSpec.describe Projects::SnippetsController do end it 'renders the snippet' do - get :index, params: { namespace_id: project.namespace, project_id: project } + subject expect(assigns(:snippets)).to include(project_snippet) expect(response).to have_gitlab_http_status(:ok) @@ -69,7 +77,7 @@ RSpec.describe Projects::SnippetsController do end it 'renders the snippet' do - get :index, params: { namespace_id: project.namespace, project_id: project } + subject expect(assigns(:snippets)).to include(project_snippet) expect(response).to have_gitlab_http_status(:ok) diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 70df1faf7dd..92370b3381a 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -6,6 +6,8 @@ RSpec.describe SnippetsController do let_it_be(:user) { create(:user) } describe 'GET #index' do + let(:base_params) { { username: user.username } } + context 'when username parameter is present' do it_behaves_like 'paginated collection' do let(:collection) { Snippet.all } @@ -38,6 +40,10 @@ RSpec.describe SnippetsController do expect(response).to redirect_to(dashboard_snippets_path) end end + + it_behaves_like 'snippets sort order' do + let(:params) { base_params } + end end describe 'GET #new' do diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 0affc832b30..cb49d5fd135 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -295,6 +295,22 @@ RSpec.describe SnippetsFinder do expect(finder.execute).to be_empty end end + + context 'no sort param is provided' do + it 'returns snippets sorted by id' do + snippets = described_class.new(admin).execute + + expect(snippets.ids).to eq(Snippet.order_id_desc.ids) + end + end + + context 'sort param is provided' do + it 'returns snippets sorted by sort param' do + snippets = described_class.new(admin, sort: 'updated_desc').execute + + expect(snippets.ids).to eq(Snippet.order_updated_desc.ids) + end + end end it_behaves_like 'snippet visibility' diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index ad01e4c3a9b..ee5af6380c4 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -948,6 +948,25 @@ describe('Monitoring store actions', () => { ); }); + it('dispatches receiveDashboardValidationWarningsSuccess with false payload when the response is empty ', () => { + mockMutate.mockResolvedValue({ + data: { + project: null, + }, + }); + + return testAction( + fetchDashboardValidationWarnings, + null, + state, + [], + [{ type: 'receiveDashboardValidationWarningsSuccess', payload: false }], + () => { + expect(mockMutate).toHaveBeenCalledWith(mutationVariables); + }, + ); + }); + it('dispatches receiveDashboardValidationWarningsFailure if the warnings API call fails', () => { mockMutate.mockRejectedValue({}); diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index ba1dfb1029b..af5fefd1bc4 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -235,6 +235,31 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do end end + context 'for plan' do + it 'includes accurate usage_activity_by_stage data' do + for_defined_days_back do + user = create(:user) + project = create(:project, creator: user) + issue = create(:issue, project: project, author: user) + create(:note, project: project, noteable: issue, author: user) + create(:todo, project: project, target: issue, author: user) + end + + expect(described_class.uncached_data[:usage_activity_by_stage][:plan]).to include( + issues: 2, + notes: 2, + projects: 2, + todos: 2 + ) + expect(described_class.uncached_data[:usage_activity_by_stage_monthly][:plan]).to include( + issues: 1, + notes: 1, + projects: 1, + todos: 1 + ) + end + end + context 'for release' do it 'includes accurate usage_activity_by_stage data' do for_defined_days_back do diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb index 50ce1b33d17..2f3cb2e998a 100644 --- a/spec/policies/merge_request_policy_spec.rb +++ b/spec/policies/merge_request_policy_spec.rb @@ -24,6 +24,7 @@ RSpec.describe MergeRequestPolicy do mr_perms = %i[create_merge_request_in create_merge_request_from read_merge_request + approve_merge_request create_note].freeze shared_examples_for 'a denied user' do diff --git a/spec/services/merge_requests/approval_service_spec.rb b/spec/services/merge_requests/approval_service_spec.rb index 68b9caa30ab..124501f17d5 100644 --- a/spec/services/merge_requests/approval_service_spec.rb +++ b/spec/services/merge_requests/approval_service_spec.rb @@ -11,6 +11,10 @@ RSpec.describe MergeRequests::ApprovalService do subject(:service) { described_class.new(project, user) } + before do + project.add_developer(user) + end + context 'with invalid approval' do before do allow(merge_request.approvals).to receive(:new).and_return(double(save: false)) @@ -56,5 +60,15 @@ RSpec.describe MergeRequests::ApprovalService do end end end + + context 'user cannot update the merge request' do + before do + project.add_guest(user) + end + + it 'does not update approvals' do + expect { service.execute(merge_request) }.not_to change { merge_request.approvals.size } + end + end end end diff --git a/spec/services/merge_requests/remove_approval_service_spec.rb b/spec/services/merge_requests/remove_approval_service_spec.rb index 89efd581abf..40da928e832 100644 --- a/spec/services/merge_requests/remove_approval_service_spec.rb +++ b/spec/services/merge_requests/remove_approval_service_spec.rb @@ -33,5 +33,14 @@ RSpec.describe MergeRequests::RemoveApprovalService do execute! end end + + context 'with a user who has not approved' do + it 'does not create an unapproval note and triggers web hook' do + expect(service).not_to receive(:execute_hooks) + expect(SystemNoteService).not_to receive(:unapprove_mr) + + execute! + end + end end end diff --git a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb new file mode 100644 index 00000000000..aa4d78b23f4 --- /dev/null +++ b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'snippets sort order' do + let(:params) { {} } + let(:sort_argument) { {} } + let(:sort_params) { params.merge(sort_argument)} + + before do + sign_in(user) + + stub_snippet_counter + end + + subject { get :index, params: sort_params } + + context 'when no sort param is provided' do + it 'calls SnippetsFinder with updated_at sort option' do + expect(SnippetsFinder).to receive(:new).with(user, + hash_including(sort: 'updated_desc')).and_call_original + + subject + end + end + + context 'when sort param is provided' do + let(:order) { 'created_desc' } + let(:sort_argument) { { sort: order } } + + it 'calls SnippetsFinder with the given sort param' do + expect(SnippetsFinder).to receive(:new).with(user, + hash_including(sort: order)).and_call_original + + subject + end + end + + def stub_snippet_counter + allow(Snippets::CountService) + .to receive(:new).and_return(double(:count_service, execute: {})) + end +end diff --git a/spec/support/shared_examples/views/plain_text_email.rb b/spec/support/shared_examples/views/plain_text_email.rb new file mode 100644 index 00000000000..23f9262b446 --- /dev/null +++ b/spec/support/shared_examples/views/plain_text_email.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'renders plain text email correctly' do + it 'renders the email without HTML links' do + render + + expect(rendered).to have_no_selector('a') + end +end diff --git a/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb b/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb new file mode 100644 index 00000000000..ce402533496 --- /dev/null +++ b/spec/views/notify/push_to_merge_request_email.text.haml_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe 'notify/push_to_merge_request_email.text.haml' do + let(:user) { create(:user, developer_projects: [project]) } + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, :simple, source_project: project) } + let(:new_commits) { project.repository.commits_between('be93687618e4b132087f430a4d8fc3a609c9b77c', '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') } + + before do + assign(:updated_by_user, user) + assign(:project, project) + assign(:merge_request, merge_request) + assign(:existing_commits, []) + assign(:new_commits, new_commits) + end + + it_behaves_like 'renders plain text email correctly' +end diff --git a/yarn.lock b/yarn.lock index 0f5b6a4c541..04eb2022c2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -848,10 +848,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.151.0.tgz#099905295d33eb31033f4a48eb3652da2f686239" integrity sha512-2PTSM8CFhUjeTFKfcq6E/YwPpOVdSVWupf3NhKO/bz/cisSBS5P7aWxaXKIaxy28ySyBKEfKaAT6b4rXTwvVgg== -"@gitlab/ui@17.19.1": - version "17.19.1" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.19.1.tgz#48c6d542b19fd6a420d22017e7026190aba1fd31" - integrity sha512-RA4QXzVWOjbK3gjX78Luhtmo1z6td1uOu8S01v+yu5Pc00HKIgN6pdDwPK8+WLCK2cnu368c457A901wSr82Gg== +"@gitlab/ui@17.21.0": + version "17.21.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.21.0.tgz#e881bac4540e3db29ee32e1dfd452677a445cd10" + integrity sha512-Ijh3QPlB3Y10Sk0f0eZ/rgRIKHGSzAWZLugw9sb+ppcn9OPbb+2vk0ZgCcdIrfkrX3G8tD8q0Ndl3K1nrz6a5g== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |