Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
99551d4458
commit
bc12365ae0
|
@ -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.
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
fragment Count on InstanceStatisticsMeasurement {
|
||||
count
|
||||
recordedAt
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -125,10 +125,6 @@
|
|||
content: '\f077';
|
||||
}
|
||||
|
||||
.fa-github::before {
|
||||
content: '\f09b';
|
||||
}
|
||||
|
||||
.fa-paperclip::before {
|
||||
content: '\f0c6';
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
= form_for @project, url: project_pages_path(@project), html: { class: 'inline', title: pages_https_only_title } do |f|
|
||||
- if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https
|
||||
= render_if_exists 'shared/pages/max_pages_size_input', form: f
|
||||
|
||||
- if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https
|
||||
|
||||
.form-group
|
||||
.form-check
|
||||
= f.check_box :pages_https_only, class: 'form-check-input', disabled: pages_https_only_disabled?
|
||||
|
@ -10,4 +11,4 @@
|
|||
= s_('GitLabPages|Force HTTPS (requires valid certificates)')
|
||||
|
||||
.gl-mt-3
|
||||
= f.submit s_('GitLabPages|Save'), class: 'btn btn-success'
|
||||
= f.submit s_('GitLabPages|Save'), class: 'btn btn-success gl-button'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add LSIF to Go Auto DevOps gitlab-ci.yml
|
||||
merge_request: 40072
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update GraphQL discussionToggleResolve mutation input id to be more type-specific
|
||||
merge_request: 45346
|
||||
author:
|
||||
type: changed
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace fa-github with GitLab SVG MERGE_REQUEST_ID
|
||||
merge_request: 45533
|
||||
author:
|
||||
type: changed
|
|
@ -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` |
|
||||
|
|
|
@ -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!
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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]
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
require 'spec_helper.rb'
|
||||
|
||||
RSpec.describe Issues::BuildService do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let(:user) { developer }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
before_all do
|
||||
project.add_developer(developer)
|
||||
project.add_guest(guest)
|
||||
end
|
||||
|
||||
def build_issue(issue_params = {})
|
||||
|
@ -134,6 +137,7 @@ RSpec.describe Issues::BuildService do
|
|||
end
|
||||
|
||||
describe '#execute' do
|
||||
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)
|
||||
|
@ -147,18 +151,42 @@ RSpec.describe Issues::BuildService do
|
|||
|
||||
expect(issue.milestone).to be_nil
|
||||
end
|
||||
|
||||
context 'setting issue type' do
|
||||
it 'sets the issue_type on the issue' do
|
||||
issue = build_issue(issue_type: 'incident')
|
||||
|
||||
expect(issue.issue_type).to eq('incident')
|
||||
end
|
||||
|
||||
context 'as guest' do
|
||||
let(:user) { guest }
|
||||
|
||||
it 'cannot set milestone' do
|
||||
milestone = create(:milestone, project: project)
|
||||
issue = build_issue(milestone_id: milestone.id)
|
||||
|
||||
expect(issue.milestone).to be_nil
|
||||
end
|
||||
|
||||
context 'setting issue type' do
|
||||
it 'defaults to issue if issue_type not given' do
|
||||
issue = build_issue
|
||||
|
||||
expect(issue.issue_type).to eq('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).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
|
||||
|
|
Loading…
Reference in New Issue