summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml2
-rw-r--r--.rubocop_todo.yml2
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/components/app.vue11
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/components/users_chart.vue143
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/constants.js2
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/fragments/count.fragment.graphql4
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql14
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/graphql/queries/users.query.graphql13
-rw-r--r--app/assets/stylesheets/fontawesome_custom.scss4
-rw-r--r--app/graphql/mutations/discussions/toggle_resolve.rb7
-rw-r--r--app/services/boards/issues/list_service.rb5
-rw-r--r--app/services/issues/build_service.rb20
-rw-r--r--app/views/import/github/new.html.haml4
-rw-r--r--app/views/import/github/status.html.haml6
-rw-r--r--app/views/projects/_import_project_pane.html.haml3
-rw-r--r--app/views/projects/pages/_pages_settings.html.haml7
-rw-r--r--app/views/shared/issuable/form/_type_selector.html.haml2
-rw-r--r--changelogs/unreleased/216438-add-lsif-to-go-auto-devops-gitlab-ci-yml.yml5
-rw-r--r--changelogs/unreleased/268042-graphql-use-new-global_id-for-discussion-mutations.yml5
-rw-r--r--changelogs/unreleased/john_long-update_project_pages_settings.yml5
-rw-r--r--changelogs/unreleased/mw-replace-fa-github-icons.yml5
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md4
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql4
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json4
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/topics/autodevops/customize.md1
-rw-r--r--doc/topics/autodevops/index.md1
-rw-r--r--doc/topics/autodevops/stages.md4
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Intelligence.gitlab-ci.yml16
-rw-r--r--lib/gitlab/metrics/requests_rack_middleware.rb20
-rw-r--r--locale/gitlab.pot9
-rw-r--r--qa/qa/page/project/new.rb4
-rw-r--r--qa/qa/service/praefect_manager.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb11
-rw-r--r--spec/features/incidents/user_creates_new_incident_spec.rb41
-rw-r--r--spec/features/projects/pages_spec.rb2
-rw-r--r--spec/frontend/analytics/instance_statistics/components/app_spec.js5
-rw-r--r--spec/frontend/analytics/instance_statistics/components/users_chart_spec.js200
-rw-r--r--spec/frontend/analytics/instance_statistics/mock_data.js12
-rw-r--r--spec/graphql/mutations/discussions/toggle_resolve_spec.rb4
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb10
-rw-r--r--spec/services/issues/build_service_spec.rb70
43 files changed, 594 insertions, 103 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index c0f53afa039..4b25908aa6a 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -4,7 +4,7 @@
- .docs:rules:review-docs
image: ruby:2.6-alpine
stage: review
- dependencies: []
+ needs: []
variables:
# We're cloning the repo instead of downloading the script for now
# because some repos are private and CI_JOB_TOKEN cannot access files.
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 8c9e2e30a5b..21599a74a56 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1274,14 +1274,12 @@ Graphql/IDType:
Exclude:
- 'ee/app/graphql/ee/mutations/issues/update.rb'
- 'ee/app/graphql/ee/types/boards/board_issue_input_base_type.rb'
- - 'ee/app/graphql/mutations/instance_security_dashboard/remove_project.rb'
- 'ee/app/graphql/mutations/issues/set_epic.rb'
- 'ee/app/graphql/mutations/iterations/update.rb'
- 'ee/app/graphql/resolvers/iterations_resolver.rb'
- 'app/graphql/mutations/boards/create.rb'
- 'app/graphql/mutations/boards/issues/issue_move_list.rb'
- 'app/graphql/mutations/boards/lists/update.rb'
- - 'app/graphql/mutations/discussions/toggle_resolve.rb'
- 'app/graphql/mutations/issues/update.rb'
- 'app/graphql/mutations/metrics/dashboard/annotations/delete.rb'
- 'app/graphql/mutations/snippets/destroy.rb'
diff --git a/app/assets/javascripts/analytics/instance_statistics/components/app.vue b/app/assets/javascripts/analytics/instance_statistics/components/app.vue
index 64c1a2565be..7aa5c98aa0b 100644
--- a/app/assets/javascripts/analytics/instance_statistics/components/app.vue
+++ b/app/assets/javascripts/analytics/instance_statistics/components/app.vue
@@ -1,19 +1,30 @@
<script>
import InstanceCounts from './instance_counts.vue';
import PipelinesChart from './pipelines_chart.vue';
+import UsersChart from './users_chart.vue';
+import { TODAY, TOTAL_DAYS_TO_SHOW, START_DATE } from '../constants';
export default {
name: 'InstanceStatisticsApp',
components: {
InstanceCounts,
PipelinesChart,
+ UsersChart,
},
+ TOTAL_DAYS_TO_SHOW,
+ START_DATE,
+ TODAY,
};
</script>
<template>
<div>
<instance-counts />
+ <users-chart
+ :start-date="$options.START_DATE"
+ :end-date="$options.TODAY"
+ :total-data-points="$options.TOTAL_DAYS_TO_SHOW"
+ />
<pipelines-chart />
</div>
</template>
diff --git a/app/assets/javascripts/analytics/instance_statistics/components/users_chart.vue b/app/assets/javascripts/analytics/instance_statistics/components/users_chart.vue
new file mode 100644
index 00000000000..a4a1d40b70b
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/components/users_chart.vue
@@ -0,0 +1,143 @@
+<script>
+import { GlAlert } from '@gitlab/ui';
+import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import produce from 'immer';
+import { sortBy } from 'lodash';
+import * as Sentry from '~/sentry/wrapper';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+import { __ } from '~/locale';
+import { formatDateAsMonth } from '~/lib/utils/datetime_utility';
+import usersQuery from '../graphql/queries/users.query.graphql';
+import { getAverageByMonth } from '../utils';
+
+const sortByDate = data => sortBy(data, item => new Date(item[0]).getTime());
+
+export default {
+ name: 'UsersChart',
+ components: { GlAlert, GlAreaChart, ChartSkeletonLoader },
+ props: {
+ startDate: {
+ type: Date,
+ required: true,
+ },
+ endDate: {
+ type: Date,
+ required: true,
+ },
+ totalDataPoints: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ loadingError: null,
+ users: [],
+ pageInfo: null,
+ };
+ },
+ apollo: {
+ users: {
+ query: usersQuery,
+ variables() {
+ return {
+ first: this.totalDataPoints,
+ after: null,
+ };
+ },
+ update(data) {
+ return data.users?.nodes || [];
+ },
+ result({ data }) {
+ const {
+ users: { pageInfo },
+ } = data;
+ this.pageInfo = pageInfo;
+ this.fetchNextPage();
+ },
+ error(error) {
+ this.handleError(error);
+ },
+ },
+ },
+ i18n: {
+ yAxisTitle: __('Total users'),
+ xAxisTitle: __('Month'),
+ loadUserChartError: __('Could not load the user chart. Please refresh the page to try again.'),
+ noDataMessage: __('There is no data available.'),
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.users.loading || this.pageInfo?.hasNextPage;
+ },
+ chartUserData() {
+ const averaged = getAverageByMonth(
+ this.users.length > this.totalDataPoints
+ ? this.users.slice(0, this.totalDataPoints)
+ : this.users,
+ { shouldRound: true },
+ );
+ return sortByDate(averaged);
+ },
+ options() {
+ return {
+ xAxis: {
+ name: this.$options.i18n.xAxisTitle,
+ type: 'category',
+ axisLabel: {
+ formatter: formatDateAsMonth,
+ },
+ },
+ yAxis: {
+ name: this.$options.i18n.yAxisTitle,
+ },
+ };
+ },
+ },
+ methods: {
+ handleError(error) {
+ this.loadingError = true;
+ this.users = [];
+ Sentry.captureException(error);
+ },
+ fetchNextPage() {
+ if (this.pageInfo?.hasNextPage) {
+ this.$apollo.queries.users
+ .fetchMore({
+ variables: { first: this.totalDataPoints, after: this.pageInfo.endCursor },
+ updateQuery: (previousResult, { fetchMoreResult }) => {
+ return produce(fetchMoreResult, newUsers => {
+ // eslint-disable-next-line no-param-reassign
+ newUsers.users.nodes = [...previousResult.users.nodes, ...newUsers.users.nodes];
+ });
+ },
+ })
+ .catch(this.handleError);
+ }
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <h3>{{ $options.i18n.yAxisTitle }}</h3>
+ <gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3">
+ {{ this.$options.i18n.loadUserChartError }}
+ </gl-alert>
+ <chart-skeleton-loader v-else-if="isLoading" />
+ <gl-alert v-else-if="!chartUserData.length" variant="info" :dismissible="false" class="gl-mt-3">
+ {{ $options.i18n.noDataMessage }}
+ </gl-alert>
+ <gl-area-chart
+ v-else
+ :option="options"
+ :include-legend-avg-max="true"
+ :data="[
+ {
+ name: $options.i18n.yAxisTitle,
+ data: chartUserData,
+ },
+ ]"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/analytics/instance_statistics/constants.js b/app/assets/javascripts/analytics/instance_statistics/constants.js
index 5ea5d17c974..846c0ef408b 100644
--- a/app/assets/javascripts/analytics/instance_statistics/constants.js
+++ b/app/assets/javascripts/analytics/instance_statistics/constants.js
@@ -1,5 +1,5 @@
import { getDateInPast } from '~/lib/utils/datetime_utility';
-const TOTAL_DAYS_TO_SHOW = 365;
+export const TOTAL_DAYS_TO_SHOW = 365;
export const TODAY = new Date();
export const START_DATE = getDateInPast(TODAY, TOTAL_DAYS_TO_SHOW);
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/fragments/count.fragment.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/fragments/count.fragment.graphql
new file mode 100644
index 00000000000..40cef95c2e7
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/graphql/fragments/count.fragment.graphql
@@ -0,0 +1,4 @@
+fragment Count on InstanceStatisticsMeasurement {
+ count
+ recordedAt
+}
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql
index fd8282683d9..f14c2658674 100644
--- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql
+++ b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql
@@ -1,32 +1,34 @@
+#import "../fragments/count.fragment.graphql"
+
query getInstanceCounts {
projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: 1) {
nodes {
- count
+ ...Count
}
}
groups: instanceStatisticsMeasurements(identifier: GROUPS, first: 1) {
nodes {
- count
+ ...Count
}
}
users: instanceStatisticsMeasurements(identifier: USERS, first: 1) {
nodes {
- count
+ ...Count
}
}
issues: instanceStatisticsMeasurements(identifier: ISSUES, first: 1) {
nodes {
- count
+ ...Count
}
}
mergeRequests: instanceStatisticsMeasurements(identifier: MERGE_REQUESTS, first: 1) {
nodes {
- count
+ ...Count
}
}
pipelines: instanceStatisticsMeasurements(identifier: PIPELINES, first: 1) {
nodes {
- count
+ ...Count
}
}
}
diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/users.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/users.query.graphql
new file mode 100644
index 00000000000..6235e36eb89
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/users.query.graphql
@@ -0,0 +1,13 @@
+#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+#import "../fragments/count.fragment.graphql"
+
+query getUsersCount($first: Int, $after: String) {
+ users: instanceStatisticsMeasurements(identifier: USERS, first: $first, after: $after) {
+ nodes {
+ ...Count
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+}
diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss
index 0f786681146..a3338ff13b5 100644
--- a/app/assets/stylesheets/fontawesome_custom.scss
+++ b/app/assets/stylesheets/fontawesome_custom.scss
@@ -125,10 +125,6 @@
content: '\f077';
}
-.fa-github::before {
- content: '\f09b';
-}
-
.fa-paperclip::before {
content: '\f0c6';
}
diff --git a/app/graphql/mutations/discussions/toggle_resolve.rb b/app/graphql/mutations/discussions/toggle_resolve.rb
index 41fd22c6b55..4492da74706 100644
--- a/app/graphql/mutations/discussions/toggle_resolve.rb
+++ b/app/graphql/mutations/discussions/toggle_resolve.rb
@@ -8,7 +8,7 @@ module Mutations
description 'Toggles the resolved state of a discussion'
argument :id,
- GraphQL::ID_TYPE,
+ Types::GlobalIDType[Discussion],
required: true,
description: 'The global id of the discussion'
@@ -54,7 +54,10 @@ module Mutations
end
def find_object(id:)
- GitlabSchema.object_from_id(id, expected_type: ::Discussion)
+ # TODO: remove explicit coercion once compatibility layer has been removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = Types::GlobalIDType[Discussion].coerce_isolated_input(id)
+ GitlabSchema.find_by_gid(id)
end
def resolve!(discussion)
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index 140420a32bd..ab9d11abe98 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -80,6 +80,7 @@ module Boards
set_scope
set_non_archived
set_attempt_search_optimizations
+ set_issue_types
params
end
@@ -116,6 +117,10 @@ module Boards
end
end
+ def set_issue_types
+ params[:issue_types] = Issue::TYPES_FOR_LIST
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def board_label_ids
@board_label_ids ||= board.lists.movable.pluck(:label_id)
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index 2de6ed9fa1c..3145739fe91 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -64,20 +64,26 @@ module Issues
private
- def whitelisted_issue_params
- base_params = [:title, :description, :confidential]
- admin_params = [:milestone_id, :issue_type]
+ def allowed_issue_base_params
+ [:title, :description, :confidential, :issue_type]
+ end
+
+ def allowed_issue_admin_params
+ [:milestone_id]
+ end
+ def allowed_issue_params
if can?(current_user, :admin_issue, project)
- params.slice(*(base_params + admin_params))
+ params.slice(*(allowed_issue_base_params + allowed_issue_admin_params))
else
- params.slice(*base_params)
+ params.slice(*allowed_issue_base_params)
end
end
def build_issue_params
- { author: current_user }.merge(issue_params_with_info_from_discussions)
- .merge(whitelisted_issue_params)
+ { author: current_user }
+ .merge(issue_params_with_info_from_discussions)
+ .merge(allowed_issue_params)
end
end
end
diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml
index e86d4236be8..7e49cad7902 100644
--- a/app/views/import/github/new.html.haml
+++ b/app/views/import/github/new.html.haml
@@ -10,7 +10,9 @@
= import_github_authorize_message
- if github_import_configured? && !has_ci_cd_only_params?
- = link_to icon('github', text: title), status_import_github_path, class: 'btn btn-success'
+ = link_to status_import_github_path, class: 'btn btn-success gl-button' do
+ = sprite_icon('github', css_class: 'gl-mr-2')
+ = title
%hr
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index ee295e70cce..ba6a5657d12 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -2,7 +2,9 @@
- page_title title
- breadcrumb_title title
- header_title _("Projects"), root_path
-%h3.page-title.mb-0
- = icon 'github', class: 'fa-2x', text: _('Import repositories from GitHub')
+%h3.page-title.mb-0.gl-display-flex
+ .gl-display-flex.gl-align-items-center.gl-justify-content-center
+ = sprite_icon('github', css_class: 'gl-mr-2')
+ = _('Import repositories from GitHub')
= render 'import/githubish_status', provider: 'github'
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 58f1d7b4990..8b94133fd8a 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -15,7 +15,8 @@
- if github_import_enabled?
%div
= link_to new_import_github_path, class: 'btn js-import-github', **tracking_attrs(track_label, 'click_button', 'github') do
- = icon('github', text: 'GitHub')
+ = sprite_icon('github')
+ GitHub
- if bitbucket_import_enabled?
%div
diff --git a/app/views/projects/pages/_pages_settings.html.haml b/app/views/projects/pages/_pages_settings.html.haml
index 8aa02074205..51483176d6f 100644
--- a/app/views/projects/pages/_pages_settings.html.haml
+++ b/app/views/projects/pages/_pages_settings.html.haml
@@ -1,6 +1,7 @@
= form_for @project, url: project_pages_path(@project), html: { class: 'inline', title: pages_https_only_title } do |f|
+ = render_if_exists 'shared/pages/max_pages_size_input', form: f
+
- if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https
- = render_if_exists 'shared/pages/max_pages_size_input', form: f
.form-group
.form-check
@@ -9,5 +10,5 @@
%strong
= s_('GitLabPages|Force HTTPS (requires valid certificates)')
- .gl-mt-3
- = f.submit s_('GitLabPages|Save'), class: 'btn btn-success'
+ .gl-mt-3
+ = f.submit s_('GitLabPages|Save'), class: 'btn btn-success gl-button'
diff --git a/app/views/shared/issuable/form/_type_selector.html.haml b/app/views/shared/issuable/form/_type_selector.html.haml
index 0281c093636..3347966f39a 100644
--- a/app/views/shared/issuable/form/_type_selector.html.haml
+++ b/app/views/shared/issuable/form/_type_selector.html.haml
@@ -1,4 +1,4 @@
-- return unless issuable.supports_issue_type? && can?(current_user, :admin_issue, @project)
+- return unless issuable.supports_issue_type? && can?(current_user, :create_issue, @project)
.form-group.row.gl-mb-0
= form.label :type, 'Type', class: 'col-form-label col-sm-2'
diff --git a/changelogs/unreleased/216438-add-lsif-to-go-auto-devops-gitlab-ci-yml.yml b/changelogs/unreleased/216438-add-lsif-to-go-auto-devops-gitlab-ci-yml.yml
new file mode 100644
index 00000000000..08df708e2da
--- /dev/null
+++ b/changelogs/unreleased/216438-add-lsif-to-go-auto-devops-gitlab-ci-yml.yml
@@ -0,0 +1,5 @@
+---
+title: Add LSIF to Go Auto DevOps gitlab-ci.yml
+merge_request: 40072
+author:
+type: added
diff --git a/changelogs/unreleased/268042-graphql-use-new-global_id-for-discussion-mutations.yml b/changelogs/unreleased/268042-graphql-use-new-global_id-for-discussion-mutations.yml
new file mode 100644
index 00000000000..45cace04adc
--- /dev/null
+++ b/changelogs/unreleased/268042-graphql-use-new-global_id-for-discussion-mutations.yml
@@ -0,0 +1,5 @@
+---
+title: Update GraphQL discussionToggleResolve mutation input id to be more type-specific
+merge_request: 45346
+author:
+type: changed
diff --git a/changelogs/unreleased/john_long-update_project_pages_settings.yml b/changelogs/unreleased/john_long-update_project_pages_settings.yml
new file mode 100644
index 00000000000..45fde761968
--- /dev/null
+++ b/changelogs/unreleased/john_long-update_project_pages_settings.yml
@@ -0,0 +1,5 @@
+---
+title: Allow size limit to be available by default in the project pages settings form
+merge_request: 45054
+author:
+type: fixed
diff --git a/changelogs/unreleased/mw-replace-fa-github-icons.yml b/changelogs/unreleased/mw-replace-fa-github-icons.yml
new file mode 100644
index 00000000000..4959d502263
--- /dev/null
+++ b/changelogs/unreleased/mw-replace-fa-github-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Replace fa-github with GitLab SVG MERGE_REQUEST_ID
+merge_request: 45533
+author:
+type: changed
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 3888c8f1bee..adb1f719f3c 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -91,8 +91,8 @@ The following metrics are available:
| `gitlab_transaction_rails_queue_duration_total` | Counter | 9.4 | Measures latency between GitLab Workhorse forwarding a request to Rails | `controller`, `action` |
| `gitlab_transaction_view_duration_total` | Counter | 9.4 | Duration for views | `controller`, `action`, `view` |
| `gitlab_view_rendering_duration_seconds` | Histogram | 10.2 | Duration for views (histogram) | `controller`, `action`, `view` |
-| `http_requests_total` | Counter | 9.4 | Rack request count | `method` |
-| `http_request_duration_seconds` | Histogram | 9.4 | HTTP response time from rack middleware | `method`, `status` |
+| `http_requests_total` | Counter | 9.4 | Rack request count | `method`, `status` |
+| `http_request_duration_seconds` | Histogram | 9.4 | HTTP response time from rack middleware | `method` |
| `gitlab_transaction_db_count_total` | Counter | 13.1 | Counter for total number of SQL calls | `controller`, `action` |
| `gitlab_transaction_db_write_count_total` | Counter | 13.1 | Counter for total number of write SQL calls | `controller`, `action` |
| `gitlab_transaction_db_cached_count_total` | Counter | 13.1 | Counter for total number of cached SQL calls | `controller`, `action` |
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index f5a7b9839b1..a44f8f70311 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -5870,7 +5870,7 @@ input DiscussionToggleResolveInput {
"""
The global id of the discussion
"""
- id: ID!
+ id: DiscussionID!
"""
Will resolve the discussion when true, and unresolve the discussion when false
@@ -16396,7 +16396,7 @@ input RemoveProjectFromSecurityDashboardInput {
"""
ID of the project to remove from the Instance Security Dashboard
"""
- id: ID!
+ id: ProjectID!
}
"""
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 8edd30c0172..6914ba29c57 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -16105,7 +16105,7 @@
"name": null,
"ofType": {
"kind": "SCALAR",
- "name": "ID",
+ "name": "DiscussionID",
"ofType": null
}
},
@@ -47191,7 +47191,7 @@
"name": null,
"ofType": {
"kind": "SCALAR",
- "name": "ID",
+ "name": "ProjectID",
"ofType": null
}
},
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 9bea4a707d7..dca6d8baa79 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -24,7 +24,7 @@ webcast to learn about continuous methods and how GitLab’s built-in CI can hel
## Overview
Continuous Integration works by pushing small code chunks to your
-application's code base hosted in a Git repository, and to every
+application's codebase hosted in a Git repository, and to every
push, run a pipeline of scripts to build, test, and validate the
code changes before merging them into the main branch.
diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md
index bd6be48ecef..0026cf4d18a 100644
--- a/doc/topics/autodevops/customize.md
+++ b/doc/topics/autodevops/customize.md
@@ -392,6 +392,7 @@ The following table lists variables used to disable jobs.
| `SAST_DISABLED` | From GitLab 11.0, used to disable the `sast` job. If the variable is present, the job won't be created. |
| `TEST_DISABLED` | From GitLab 11.0, used to disable the `test` job. If the variable is present, the job won't be created. |
| `SECRET_DETECTION_DISABLED` | From GitLab 13.1, used to disable the `secret_detection` job. If the variable is present, the job won't be created. |
+| `CODE_INTELLIGENCE_DISABLED` | From GitLab 13.6, used to disable the `code_intelligence` job. If the variable is present, the job won't be created. |
### Application secret variables
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 2a4ca3977a5..1952fadc076 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -95,6 +95,7 @@ project in a simple and automatic way:
1. [Auto Deploy](stages.md#auto-deploy)
1. [Auto Browser Performance Testing](stages.md#auto-browser-performance-testing) **(PREMIUM)**
1. [Auto Monitoring](stages.md#auto-monitoring)
+1. [Auto Code Intelligence](stages.md#auto-code-intelligence)
As Auto DevOps relies on many different components, you should have a basic
knowledge of the following:
diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md
index 3fec3c4c621..d3f02889a2e 100644
--- a/doc/topics/autodevops/stages.md
+++ b/doc/topics/autodevops/stages.md
@@ -665,3 +665,7 @@ To use Auto Monitoring:
whole Kubernetes cluster, navigate to **Operations > Metrics**.
![Auto Metrics](img/auto_monitoring.png)
+
+## Auto Code Intelligence
+
+Code Intelligence is powered by [LSIF](https://lsif.dev/) and available for Go at this stage. We'll support more languages as they become available.
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 6966ce88b30..cba13f374f4 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -20,6 +20,7 @@
# * dast: DAST_DISABLED
# * review: REVIEW_DISABLED
# * stop_review: REVIEW_DISABLED
+# * code_intelligence: CODE_INTELLIGENCE_DISABLED
#
# In order to deploy, you must have a Kubernetes cluster configured either
# via a project integration, or via group/project variables.
@@ -159,6 +160,7 @@ include:
- template: Jobs/Build.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
- template: Jobs/Test.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml
- template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+ - template: Jobs/Code-Intelligence.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Code-Intelligence.gitlab-ci.yml
- template: Jobs/Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
- template: Jobs/Deploy/ECS.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy/ECS.gitlab-ci.yml
- template: Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Intelligence.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Intelligence.gitlab-ci.yml
new file mode 100644
index 00000000000..83bc5548614
--- /dev/null
+++ b/lib/gitlab/ci/templates/Jobs/Code-Intelligence.gitlab-ci.yml
@@ -0,0 +1,16 @@
+code_intelligence_go:
+ stage: test
+ needs: []
+ allow_failure: true
+ image: sourcegraph/lsif-go:v1
+ rules:
+ - if: $CODE_INTELLIGENCE_DISABLED
+ when: never
+ - if: $CI_COMMIT_BRANCH
+ exists:
+ - '**/*.go'
+ script:
+ - lsif-go
+ artifacts:
+ reports:
+ lsif: dump.lsif
diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb
index 99a44c5eeb9..f6bda0dbea4 100644
--- a/lib/gitlab/metrics/requests_rack_middleware.rb
+++ b/lib/gitlab/metrics/requests_rack_middleware.rb
@@ -3,15 +3,7 @@
module Gitlab
module Metrics
class RequestsRackMiddleware
- HTTP_METHODS = {
- "delete" => %w(200 202 204 303 400 401 403 404 500 503),
- "get" => %w(200 204 301 302 303 304 307 400 401 403 404 410 422 429 500 503),
- "head" => %w(200 204 301 302 303 401 403 404 410 500),
- "options" => %w(200 404),
- "patch" => %w(200 202 204 400 403 404 409 416 500),
- "post" => %w(200 201 202 204 301 302 303 304 400 401 403 404 406 409 410 412 422 429 500 503),
- "put" => %w(200 202 204 400 401 403 404 405 406 409 410 422 500)
- }.freeze
+ HTTP_METHODS = %w(delete get head options patch post put).to_set.freeze
HEALTH_ENDPOINT = /^\/-\/(liveness|readiness|health|metrics)\/?$/.freeze
@@ -40,16 +32,14 @@ module Gitlab
end
def self.initialize_http_request_duration_seconds
- HTTP_METHODS.each do |method, statuses|
- statuses.each do |status|
- http_request_duration_seconds.get({ method: method, status: status.to_s })
- end
+ HTTP_METHODS.each do |method|
+ http_request_duration_seconds.get({ method: method })
end
end
def call(env)
method = env['REQUEST_METHOD'].downcase
- method = 'INVALID' unless HTTP_METHODS.key?(method)
+ method = 'INVALID' unless HTTP_METHODS.include?(method)
started = Time.now.to_f
health_endpoint = health_endpoint?(env['PATH_INFO'])
status = 'undefined'
@@ -62,7 +52,7 @@ module Gitlab
feature_category = headers&.fetch(FEATURE_CATEGORY_HEADER, nil)
unless health_endpoint
- RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method, status: status.to_s }, elapsed)
+ RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method }, elapsed)
end
[status, headers, body]
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 73f2116facc..2eff8d1365f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7513,6 +7513,9 @@ msgstr ""
msgid "Could not load instance counts. Please refresh the page to try again."
msgstr ""
+msgid "Could not load the user chart. Please refresh the page to try again."
+msgstr ""
+
msgid "Could not remove the trigger."
msgstr ""
@@ -26510,6 +26513,9 @@ msgstr ""
msgid "There is no chart data available."
msgstr ""
+msgid "There is no data available."
+msgstr ""
+
msgid "There is no data available. Please change your selection."
msgstr ""
@@ -27780,6 +27786,9 @@ msgstr ""
msgid "Total test time for all commits/merges"
msgstr ""
+msgid "Total users"
+msgstr ""
+
msgid "Total weight"
msgstr ""
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index bfc825990b6..7e296528795 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -23,10 +23,6 @@ module QA
element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
- view 'app/views/projects/_import_project_pane.html.haml' do
- element :import_github, "icon('github', text: 'GitHub')" # rubocop:disable QA/ElementWithPattern
- end
-
view 'app/views/projects/project_templates/_template.html.haml' do
element :use_template_button
element :template_option_row
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index fb2019394b4..3a17acbb317 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -387,7 +387,7 @@ module QA
end
def verify_storage_move_to_praefect(repo_path, virtual_storage)
- wait_until_shell_command("docker exec #{@gitlab} bash -c 'tail -n 50 /var/log/gitlab/praefect/current'") do |line|
+ wait_until_shell_command("docker exec #{@praefect} bash -c 'tail -n 50 /var/log/gitlab/praefect/current'") do |line|
log = JSON.parse(line)
log['grpc.method'] == 'ReplicateRepository' && log['virtual_storage'] == virtual_storage && log['relative_path'] == repo_path
diff --git a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
index 758ba582929..e96b9ad9258 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
@@ -4,7 +4,6 @@ module QA
RSpec.describe 'Create' do
describe 'Changing Gitaly repository storage', :requires_admin do
praefect_manager = Service::PraefectManager.new
- praefect_manager.gitlab = 'gitlab'
shared_examples 'repository storage move' do
it 'confirms a `finished` status after moving project repository storage' do
@@ -28,7 +27,6 @@ module QA
context 'when moving from one Gitaly storage to another', :orchestrated, :repository_storage, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/973' do
let(:source_storage) { { type: :gitaly, name: 'default' } }
let(:destination_storage) { { type: :gitaly, name: QA::Runtime::Env.additional_repository_storage } }
-
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'repo-storage-move-status'
@@ -37,6 +35,10 @@ module QA
end
end
+ before do
+ praefect_manager.gitlab = 'gitlab'
+ end
+
it_behaves_like 'repository storage move'
end
@@ -46,7 +48,6 @@ module QA
context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/974' do
let(:source_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } }
let(:destination_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } }
-
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'repo-storage-move'
@@ -56,6 +57,10 @@ module QA
end
end
+ before do
+ praefect_manager.gitlab = 'gitlab-gitaly-cluster'
+ end
+
it_behaves_like 'repository storage move'
end
end
diff --git a/spec/features/incidents/user_creates_new_incident_spec.rb b/spec/features/incidents/user_creates_new_incident_spec.rb
index 9b9cf396c0a..99a137b5852 100644
--- a/spec/features/incidents/user_creates_new_incident_spec.rb
+++ b/spec/features/incidents/user_creates_new_incident_spec.rb
@@ -5,34 +5,51 @@ require 'spec_helper'
RSpec.describe 'Incident Management index', :js do
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user) }
+ let_it_be(:guest) { create(:user) }
let_it_be(:incident) { create(:incident, project: project) }
before_all do
project.add_developer(developer)
+ project.add_guest(guest)
end
- before do
- sign_in(developer)
-
- visit project_incidents_path(project)
- wait_for_requests
- end
-
- context 'when a developer displays the incident list' do
+ shared_examples 'create incident form' do
it 'shows the create new issue button' do
expect(page).to have_selector('.create-incident-button')
end
- it 'shows the create issue page with the Incident type pre-selected when clicked' do
+ it 'when clicked shows the create issue page with the Incident type pre-selected' do
find('.create-incident-button').click
- wait_for_requests
+ wait_for_all_requests
- expect(page).to have_selector(".dropdown-menu-toggle")
- expect(page).to have_selector(".js-issuable-type-filter-dropdown-wrap")
+ expect(page).to have_selector('.dropdown-menu-toggle')
+ expect(page).to have_selector('.js-issuable-type-filter-dropdown-wrap')
page.within('.js-issuable-type-filter-dropdown-wrap') do
expect(page).to have_content('Incident')
end
end
end
+
+ context 'when a developer displays the incident list' do
+ before do
+ sign_in(developer)
+
+ visit project_incidents_path(project)
+ wait_for_all_requests
+ end
+
+ it_behaves_like 'create incident form'
+ end
+
+ context 'when a guest displays the incident list' do
+ before do
+ sign_in(guest)
+
+ visit project_incidents_path(project)
+ wait_for_all_requests
+ end
+
+ it_behaves_like 'create incident form'
+ end
end
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index 243579ee2f7..c3eea0195a6 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -336,7 +336,7 @@ RSpec.shared_examples 'pages settings editing' do
expect(page).not_to have_field(:project_pages_https_only)
expect(page).not_to have_content('Force HTTPS (requires valid certificates)')
- expect(page).not_to have_button('Save')
+ expect(page).to have_button('Save')
end
end
end
diff --git a/spec/frontend/analytics/instance_statistics/components/app_spec.js b/spec/frontend/analytics/instance_statistics/components/app_spec.js
index 39f6d1b450a..df13c9f82a9 100644
--- a/spec/frontend/analytics/instance_statistics/components/app_spec.js
+++ b/spec/frontend/analytics/instance_statistics/components/app_spec.js
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import InstanceStatisticsApp from '~/analytics/instance_statistics/components/app.vue';
import InstanceCounts from '~/analytics/instance_statistics/components//instance_counts.vue';
import PipelinesChart from '~/analytics/instance_statistics/components/pipelines_chart.vue';
+import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue';
describe('InstanceStatisticsApp', () => {
let wrapper;
@@ -26,4 +27,8 @@ describe('InstanceStatisticsApp', () => {
it('displays the pipelines chart component', () => {
expect(wrapper.find(PipelinesChart).exists()).toBe(true);
});
+
+ it('displays the users chart component', () => {
+ expect(wrapper.find(UsersChart).exists()).toBe(true);
+ });
});
diff --git a/spec/frontend/analytics/instance_statistics/components/users_chart_spec.js b/spec/frontend/analytics/instance_statistics/components/users_chart_spec.js
new file mode 100644
index 00000000000..7509c1e6626
--- /dev/null
+++ b/spec/frontend/analytics/instance_statistics/components/users_chart_spec.js
@@ -0,0 +1,200 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import { GlAlert } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+import { useFakeDate } from 'helpers/fake_date';
+import UsersChart from '~/analytics/instance_statistics/components/users_chart.vue';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+import usersQuery from '~/analytics/instance_statistics/graphql/queries/users.query.graphql';
+import { mockCountsData2, roundedSortedCountsMonthlyChartData2, mockPageInfo } from '../mock_data';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('UsersChart', () => {
+ let wrapper;
+ let queryHandler;
+
+ const mockApolloResponse = ({ loading = false, hasNextPage = false, users }) => ({
+ data: {
+ users: {
+ pageInfo: { ...mockPageInfo, hasNextPage },
+ nodes: users,
+ loading,
+ },
+ },
+ });
+
+ const mockQueryResponse = ({ users, loading = false, hasNextPage = false }) => {
+ const apolloQueryResponse = mockApolloResponse({ loading, hasNextPage, users });
+ if (loading) {
+ return jest.fn().mockReturnValue(new Promise(() => {}));
+ }
+ if (hasNextPage) {
+ return jest
+ .fn()
+ .mockResolvedValueOnce(apolloQueryResponse)
+ .mockResolvedValueOnce(
+ mockApolloResponse({
+ loading,
+ hasNextPage: false,
+ users: [{ recordedAt: '2020-07-21', count: 5 }],
+ }),
+ );
+ }
+ return jest.fn().mockResolvedValue(apolloQueryResponse);
+ };
+
+ const createComponent = ({
+ loadingError = false,
+ loading = false,
+ users = [],
+ hasNextPage = false,
+ } = {}) => {
+ queryHandler = mockQueryResponse({ users, loading, hasNextPage });
+
+ return shallowMount(UsersChart, {
+ props: {
+ startDate: useFakeDate(2020, 9, 26),
+ endDate: useFakeDate(2020, 10, 1),
+ totalDataPoints: mockCountsData2.length,
+ },
+ localVue,
+ apolloProvider: createMockApollo([[usersQuery, queryHandler]]),
+ data() {
+ return { loadingError };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findLoader = () => wrapper.find(ChartSkeletonLoader);
+ const findAlert = () => wrapper.find(GlAlert);
+ const findChart = () => wrapper.find(GlAreaChart);
+
+ describe('while loading', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ loading: true });
+ });
+
+ it('displays the skeleton loader', () => {
+ expect(findLoader().exists()).toBe(true);
+ });
+
+ it('hides the chart', () => {
+ expect(findChart().exists()).toBe(false);
+ });
+ });
+
+ describe('without data', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({ users: [] });
+ await wrapper.vm.$nextTick();
+ });
+
+ it('renders an no data message', () => {
+ expect(findAlert().text()).toBe('There is no data available.');
+ });
+
+ it('hides the skeleton loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('renders the chart', () => {
+ expect(findChart().exists()).toBe(false);
+ });
+ });
+
+ describe('with data', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({ users: mockCountsData2 });
+ await wrapper.vm.$nextTick();
+ });
+
+ it('hides the skeleton loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('renders the chart', () => {
+ expect(findChart().exists()).toBe(true);
+ });
+
+ it('passes the data to the line chart', () => {
+ expect(findChart().props('data')).toEqual([
+ { data: roundedSortedCountsMonthlyChartData2, name: 'Total users' },
+ ]);
+ });
+ });
+
+ describe('with errors', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({ loadingError: true });
+ await wrapper.vm.$nextTick();
+ });
+
+ it('renders an error message', () => {
+ expect(findAlert().text()).toBe(
+ 'Could not load the user chart. Please refresh the page to try again.',
+ );
+ });
+
+ it('hides the skeleton loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('renders the chart', () => {
+ expect(findChart().exists()).toBe(false);
+ });
+ });
+
+ describe('when fetching more data', () => {
+ describe('when the fetchMore query returns data', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({
+ users: mockCountsData2,
+ hasNextPage: true,
+ });
+
+ jest.spyOn(wrapper.vm.$apollo.queries.users, 'fetchMore');
+ await wrapper.vm.$nextTick();
+ });
+
+ it('requests data twice', () => {
+ expect(queryHandler).toBeCalledTimes(2);
+ });
+
+ it('calls fetchMore', () => {
+ expect(wrapper.vm.$apollo.queries.users.fetchMore).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('when the fetchMore query throws an error', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ users: mockCountsData2,
+ hasNextPage: true,
+ });
+
+ jest
+ .spyOn(wrapper.vm.$apollo.queries.users, 'fetchMore')
+ .mockImplementation(jest.fn().mockRejectedValue());
+ return wrapper.vm.$nextTick();
+ });
+
+ it('calls fetchMore', () => {
+ expect(wrapper.vm.$apollo.queries.users.fetchMore).toHaveBeenCalledTimes(1);
+ });
+
+ it('renders an error message', () => {
+ expect(findAlert().text()).toBe(
+ 'Could not load the user chart. Please refresh the page to try again.',
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/analytics/instance_statistics/mock_data.js b/spec/frontend/analytics/instance_statistics/mock_data.js
index c3f5069da28..b737db4c55f 100644
--- a/spec/frontend/analytics/instance_statistics/mock_data.js
+++ b/spec/frontend/analytics/instance_statistics/mock_data.js
@@ -28,3 +28,15 @@ export const countsMonthlyChartData2 = [
['2020-07-01', 9.5], // average of 2020-07-x items
['2020-06-01', 20.666666666666668], // average of 2020-06-x items
];
+
+export const roundedSortedCountsMonthlyChartData2 = [
+ ['2020-06-01', 21], // average of 2020-06-x items
+ ['2020-07-01', 10], // average of 2020-07-x items
+];
+
+export const mockPageInfo = {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor: null,
+ endCursor: null,
+};
diff --git a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
index d779a2227c1..2e5d41a8f1e 100644
--- a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -50,8 +50,8 @@ RSpec.describe Mutations::Discussions::ToggleResolve do
it 'raises an error' do
expect { subject }.to raise_error(
- Gitlab::Graphql::Errors::ArgumentError,
- "#{discussion.to_global_id} is not a valid ID for Discussion."
+ GraphQL::CoercionError,
+ "\"#{discussion.to_global_id}\" does not represent an instance of Discussion"
)
end
end
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 375f3351ea8..631325402d9 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
end
it 'measures execution time' do
- expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: '200', method: 'get' }, a_positive_execution_time)
+ expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ method: 'get' }, a_positive_execution_time)
Timecop.scale(3600) { subject.call(env) }
end
@@ -77,7 +77,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
it 'records the request duration' do
expect(described_class)
.to receive_message_chain(:http_request_duration_seconds, :observe)
- .with({ method: 'get', status: '200' }, a_positive_execution_time)
+ .with({ method: 'get' }, a_positive_execution_time)
subject.call(env)
end
@@ -137,10 +137,8 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware do
describe '.initialize_http_request_duration_seconds' do
it "sets labels" do
expected_labels = []
- described_class::HTTP_METHODS.each do |method, statuses|
- statuses.each do |status|
- expected_labels << { method: method, status: status.to_s }
- end
+ described_class::HTTP_METHODS.each do |method|
+ expected_labels << { method: method }
end
described_class.initialize_http_request_duration_seconds
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index 93eef8a2732..16433d49ca1 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -3,11 +3,14 @@
require 'spec_helper.rb'
RSpec.describe Issues::BuildService do
- let(:project) { create(:project, :repository) }
- let(:user) { create(:user) }
-
- before do
- project.add_developer(user)
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:guest) { create(:user) }
+ let(:user) { developer }
+
+ before_all do
+ project.add_developer(developer)
+ project.add_guest(guest)
end
def build_issue(issue_params = {})
@@ -134,31 +137,56 @@ RSpec.describe Issues::BuildService do
end
describe '#execute' do
- it 'builds a new issues with given params' do
- milestone = create(:milestone, project: project)
- issue = build_issue(milestone_id: milestone.id)
+ context 'as developer' do
+ it 'builds a new issues with given params' do
+ milestone = create(:milestone, project: project)
+ issue = build_issue(milestone_id: milestone.id)
- expect(issue.milestone).to eq(milestone)
- end
+ expect(issue.milestone).to eq(milestone)
+ end
- it 'sets milestone to nil if it is not available for the project' do
- milestone = create(:milestone, project: create(:project))
- issue = build_issue(milestone_id: milestone.id)
+ it 'sets milestone to nil if it is not available for the project' do
+ milestone = create(:milestone, project: create(:project))
+ issue = build_issue(milestone_id: milestone.id)
- expect(issue.milestone).to be_nil
+ expect(issue.milestone).to be_nil
+ end
end
- context 'setting issue type' do
- it 'sets the issue_type on the issue' do
- issue = build_issue(issue_type: 'incident')
+ context 'as guest' do
+ let(:user) { guest }
- expect(issue.issue_type).to eq('incident')
+ it 'cannot set milestone' do
+ milestone = create(:milestone, project: project)
+ issue = build_issue(milestone_id: milestone.id)
+
+ expect(issue.milestone).to be_nil
end
- it 'defaults to issue if issue_type not given' do
- issue = build_issue
+ context 'setting issue type' do
+ it 'defaults to issue if issue_type not given' do
+ issue = build_issue
+
+ expect(issue).to be_issue
+ end
+
+ it 'sets issue' do
+ issue = build_issue(issue_type: 'issue')
+
+ expect(issue).to be_issue
+ end
+
+ it 'sets incident' do
+ issue = build_issue(issue_type: 'incident')
- expect(issue.issue_type).to eq('issue')
+ expect(issue).to be_incident
+ end
+
+ it 'cannot set invalid type' do
+ expect do
+ build_issue(issue_type: 'invalid type')
+ end.to raise_error(ArgumentError, "'invalid type' is not a valid issue_type")
+ end
end
end
end