Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-12 18:09:23 +00:00
parent 006000e366
commit ee4105895e
70 changed files with 588 additions and 141 deletions

View file

@ -265,7 +265,6 @@ export default {
<gl-form-checkbox
ref="masked-ci-variable"
v-model="masked"
data-qa-selector="ci_variable_masked_checkbox"
data-testid="ci-variable-masked-checkbox"
>
{{ __('Mask variable') }}

View file

@ -8,6 +8,7 @@ import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin';
import PipelinesService from '~/pipelines/services/pipelines_service';
import PipelineStore from '~/pipelines/stores/pipelines_store';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@ -19,7 +20,7 @@ export default {
TablePagination,
SvgBlankState,
},
mixins: [PipelinesMixin],
mixins: [PipelinesMixin, glFeatureFlagMixin()],
props: {
endpoint: {
type: String,
@ -90,6 +91,9 @@ export default {
canRenderPipelineButton() {
return this.latestPipelineDetachedFlag;
},
pipelineButtonClass() {
return !this.glFeatures.newPipelinesTable ? 'gl-md-display-none' : 'gl-lg-display-none';
},
isForkMergeRequest() {
return this.sourceProjectFullPath !== this.targetProjectFullPath;
},
@ -192,7 +196,8 @@ export default {
<gl-button
v-if="canRenderPipelineButton"
block
class="gl-mt-3 gl-mb-3 gl-md-display-none"
class="gl-mt-3 gl-mb-3"
:class="pipelineButtonClass"
variant="success"
data-testid="run_pipeline_button_mobile"
:loading="state.isRunningMergeRequestPipeline"

View file

@ -4,6 +4,7 @@ fragment EpicNode on Epic {
title
state
reference
webPath
webUrl
createdAt
closedAt

View file

@ -41,7 +41,6 @@ export default {
:disabled="shouldDisableNewMrOption"
:checked="shouldCreateMR"
type="checkbox"
data-qa-selector="start_new_mr_checkbox"
@change="toggleShouldCreateMR"
/>
<span class="gl-ml-3 ide-option-label">

View file

@ -122,8 +122,8 @@ export const gradleGroovyInstalCommand = ({ packageEntity }) => {
export const gradleGroovyAddSourceCommand = ({ mavenPath }) =>
// eslint-disable-next-line @gitlab/require-i18n-strings
`gitlab {
url "${mavenPath}"
`maven {
url '${mavenPath}'
}`;
export const groupExists = ({ groupListUrl }) => groupListUrl.length > 0;

View file

@ -6,7 +6,7 @@ import Translate from '~/vue_shared/translate';
import deleteProjectModal from './components/delete_project_modal.vue';
document.addEventListener('DOMContentLoaded', () => {
(() => {
Vue.use(Translate);
const deleteProjectModalEl = document.getElementById('delete-project-modal');
@ -39,4 +39,4 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
});
})();

View file

@ -106,8 +106,8 @@ export default {
height: this.$refs[this.containerId].scrollHeight,
};
},
onError(errorType) {
this.$emit('error', errorType);
onError(payload) {
this.$emit('error', payload);
},
setJob(jobName) {
this.hoveredJobName = jobName;

View file

@ -73,7 +73,11 @@ export default {
return unwrapPipelineData(this.pipelineProjectPath, data);
},
error(err) {
this.reportFailure(LOAD_FAILURE, serializeLoadErrors(err));
this.reportFailure({ type: LOAD_FAILURE, skipSentry: true });
reportToSentry(
this.$options.name,
`type: ${LOAD_FAILURE}, info: ${serializeLoadErrors(err)}`,
);
},
result({ error }) {
/*
@ -134,11 +138,15 @@ export default {
refreshPipelineGraph() {
this.$apollo.queries.pipeline.refetch();
},
reportFailure(type, err = '') {
/* eslint-disable @gitlab/require-i18n-strings */
reportFailure({ type, err = 'No error string passed.', skipSentry = false }) {
this.showAlert = true;
this.alertType = type;
reportToSentry(this.$options.name, `type: ${this.alertType}, info: ${err}`);
if (!skipSentry) {
reportToSentry(this.$options.name, `type: ${type}, info: ${err}`);
}
},
/* eslint-enable @gitlab/require-i18n-strings */
},
};
</script>

View file

@ -111,14 +111,12 @@ export default {
this.loadingPipelineId = null;
this.$emit('scrollContainer');
},
error(err, _vm, _key, type) {
this.$emit('error', LOAD_FAILURE);
error(err) {
this.$emit('error', { type: LOAD_FAILURE, skipSentry: true });
reportToSentry(
'linked_pipelines_column',
`error type: ${LOAD_FAILURE}, error: ${serializeLoadErrors(
err,
)}, apollo error type: ${type}`,
`error type: ${LOAD_FAILURE}, error: ${serializeLoadErrors(err)}`,
);
},
});

View file

@ -170,7 +170,7 @@ export default {
const parsedData = parseData(arrayOfJobs);
this.links = generateLinksData(parsedData, this.containerId, `-${this.pipelineId}`);
} catch (err) {
this.$emit('error', DRAW_FAILURE);
this.$emit('error', { type: DRAW_FAILURE, reportToSentry: false });
reportToSentry(this.$options.name, err);
}
this.finishPerfMeasureAndSend();

View file

@ -48,6 +48,9 @@ export default {
legacyTableMobileClass() {
return !this.glFeatures.newPipelinesTable ? 'table-mobile-content' : '';
},
showInProgress() {
return !this.duration && !this.finishedTime;
},
},
};
</script>
@ -57,6 +60,11 @@ export default {
{{ s__('Pipeline|Duration') }}
</div>
<div :class="legacyTableMobileClass">
<span v-if="showInProgress" data-testid="pipeline-in-progress">
<gl-icon name="hourglass" class="gl-vertical-align-baseline! gl-mr-2" :size="12" />
{{ s__('Pipeline|In progress') }}
</span>
<p v-if="duration" class="duration">
<gl-icon name="timer" class="gl-vertical-align-baseline!" :size="12" />
{{ durationFormatted }}

View file

@ -18,7 +18,7 @@ module BoardsHelper
time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s,
recent_boards_endpoint: recent_boards_path,
parent: current_board_parent.model_name.param_key,
group_id: @group&.id,
group_id: group_id,
labels_filter_base_path: build_issue_link_base,
labels_fetch_path: labels_fetch_path,
labels_manage_path: labels_manage_path,
@ -26,6 +26,12 @@ module BoardsHelper
}
end
def group_id
return @group.id if board.group_board?
@project&.group&.id
end
def full_path
if board.group_board?
@group.full_path

View file

@ -4,8 +4,8 @@
%fieldset
.form-group
.form-check
= f.check_box :performance_bar_enabled, class: 'form-check-input'
= f.label :performance_bar_enabled, class: 'form-check-label qa-enable-performance-bar-checkbox' do
= f.check_box :performance_bar_enabled, class: 'form-check-input', data: { qa_selector: 'enable_performance_bar_checkbox'}
= f.label :performance_bar_enabled, class: 'form-check-label' do
Enable access to the Performance Bar
.form-group
= f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'label-bold'

View file

@ -55,7 +55,7 @@
%div{ :class => "col-sm-12" }
.form-check
- experiment(:new_project_readme, actor: current_user) do |e|
= check_box_tag 'project[initialize_with_readme]', '1', e.run, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme", track_value: "" }
= check_box_tag 'project[initialize_with_readme]', '1', e.run, class: 'form-check-input', data: { qa_selector: "initialize_with_readme_checkbox", track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme", track_value: "" }
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
%strong= s_('ProjectsNew|Initialize repository with a README')

View file

@ -21,8 +21,8 @@
.form-actions
- if @milestone.new_record?
= f.submit _('Create milestone'), class: 'gl-button btn-success btn', data: { qa_selector: 'create_milestone_button' }
= link_to _('Cancel'), project_milestones_path(@project), class: 'gl-button btn btn-cancel'
= f.submit _('Create milestone'), class: 'gl-button btn-confirm btn', data: { qa_selector: 'create_milestone_button' }
= link_to _('Cancel'), project_milestones_path(@project), class: 'gl-button btn btn-default btn-cancel'
- else
= f.submit _('Save changes'), class: 'gl-button btn-success btn'
= link_to _('Cancel'), project_milestone_path(@project, @milestone), class: 'gl-button btn btn-cancel'
= f.submit _('Save changes'), class: 'gl-button btn-confirm btn'
= link_to _('Cancel'), project_milestone_path(@project, @milestone), class: 'gl-button btn btn-default btn-cancel'

View file

@ -8,7 +8,7 @@
= render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
= link_to new_project_milestone_path(@project), class: 'gl-button btn btn-success', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= _('New milestone')
- if @milestones.blank?

View file

@ -35,7 +35,7 @@
= link_to _('Learn more.'), help_page_path('user/project/repository/repository_mirroring', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer'
.panel-footer
= f.submit _('Mirror repository'), class: 'gl-button btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror
= f.submit _('Mirror repository'), class: 'gl-button btn btn-confirm js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror
- else
.gl-alert.gl-alert-info{ role: 'alert' }
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')

View file

@ -56,7 +56,7 @@
%tr.build-state.responsive-table-border-start
%td.responsive-table-cell.ci-status-icon-failed{ data: { column: _('Status')} }
.d-none.d-md-block.build-icon
= custom_icon("icon_status_#{build.status}")
= sprite_icon("status_#{build.status}")
.d-md-none.build-badge
= render "ci/status/badge", link: false, status: job.detailed_status(current_user)
%td.responsive-table-cell.build-name{ data: { column: _('Name')} }

View file

@ -3,7 +3,6 @@
- use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true
- stars = true unless local_assigns[:stars] == false
- forks = true unless local_assigns[:forks] == false
- merge_requests = true unless local_assigns[:merge_requests] == false
- pipeline_status = true unless local_assigns[:pipeline_status] == false
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- user = local_assigns[:user]
@ -39,8 +38,9 @@
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user, merge_requests: merge_requests,
issues: project.issues_enabled?, pipeline_status: pipeline_status, compact_mode: compact_mode
forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user,
merge_requests: project.merge_requests_enabled?, issues: project.issues_enabled?,
pipeline_status: pipeline_status, compact_mode: compact_mode
= paginate_collection(projects, remote: remote) unless skip_pagination
- else
- if @contributed_projects

View file

@ -0,0 +1,5 @@
---
title: 'Usage ping: Histogram for enabled integrations per project'
merge_request: 55782
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Move from btn-success to btn-confirm in milestones directory
merge_request: 56342
author: Yogi (@yo)
type: changed

View file

@ -0,0 +1,5 @@
---
title: Move from btn-success to btn-confirm in mirrors directory
merge_request: 56343
author: Yogi (@yo)
type: changed

View file

@ -0,0 +1,5 @@
---
title: Correct generated maven repository instruction for Gradle Groovy DSL
merge_request: 56318
author: Cromefire_ (@cromefire_)
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Hide MR count and link in project list where MRs are disabled
merge_request: !56432
author: Simon Stieger @sim0
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Display in progress for pipeline duration cell when pipeline has not finished running
merge_request: 56266
author:
type: changed

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/294190
milestone: '13.7'
type: development
group: group::optimize
default_enabled: false
default_enabled: true

View file

@ -0,0 +1,20 @@
---
key_path: usage_activity_by_stage.monitor.projects_with_enabled_alert_integrations_histogram
description: Histogram (buckets 1 to 100) of projects with at least 1 enabled integration.
product_section: ops
product_stage: monitor
product_group: group::monitor
product_category: incident_management
value_type: object
status: data_available
milestone: "13.10"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55782
time_frame: all
data_source: database
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View file

@ -22,7 +22,7 @@
},
"value_type": {
"type": "string",
"enum": ["string", "number", "boolean"]
"enum": ["string", "number", "boolean", "object"]
},
"status": {
"type": ["string"],

View file

@ -286,8 +286,8 @@ The visual review tools retrieve the merge request ID from the `data-merge-reque
data attribute included in the `script` HTML tag used to add the visual review tools
to your review app.
After determining the ID for the merge request to link to a visual review app, you
can supply the ID by either:
After determining the ID for the merge request to link to a visual review app, you
can supply the ID by either:
- Hard-coding it in the script tag via the data attribute `data-merge-request-id` of the app.
- Dynamically adding the `data-merge-request-id` value during the build of the app.

View file

@ -72,6 +72,10 @@ Please read [versioning](#versioning) section for introducing breaking change sa
When a root `.gitlab-ci.yml` [includes](../../ci/yaml/README.md#include)
multiple templates, these global keywords could be overridden by the
others and cause an unexpected behavior.
- Include [a changelog](../changelog.md) if your merge request introduces a user-facing change.
- Use [`$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH`](../../ci/variables/predefined_variables.md)
instead of a hardcoded `main` branch, and never use `master`.
- Use [`rules`](../../ci/yaml/README.md#rules) instead of [`only` or `except`](../../ci/yaml/README.md#onlyexcept-basic), if possible.
## Versioning

View file

@ -1028,6 +1028,18 @@ Status: `data_available`
Tiers: `premium`, `ultimate`
### `counts.geo_node_usage.git_fetch_event_count_weekly`
Number of Git fetch events from Prometheus on the Geo secondary
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210309194425_git_fetch_event_count_weekly.yml)
Group: `group::geo`
Status: `implemented`
Tiers: `premium`, `ultimate`
### `counts.geo_nodes`
Total number of sites in a Geo deployment
@ -14840,6 +14852,18 @@ Status: `data_available`
Tiers: `free`
### `usage_activity_by_stage.monitor.projects_with_enabled_alert_integrations_histogram`
Histogram (buckets 1 to 100) of projects with at least 1 enabled integration.
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210309165717_projects_with_enabled_alert_integrations_histogram.yml)
Group: `group::monitor`
Status: `data_available`
Tiers: `free`, `premium`, `ultimate`
### `usage_activity_by_stage.monitor.projects_with_error_tracking_enabled`
Projects where error tracking is enabled

View file

@ -32,7 +32,7 @@ Each metric is defined in a separate YAML file consisting of a number of fields:
| `product_stage` | no | The [stage](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) for the metric. |
| `product_group` | yes | The [group](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) that owns the metric. |
| `product_category` | no | The [product category](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml) for the metric. |
| `value_type` | yes | `string`; one of `string`, `number`, `boolean`. |
| `value_type` | yes | `string`; one of `string`, `number`, `boolean`, `object`. |
| `status` | yes | `string`; status of the metric, may be set to `data_available`, `planned`, `in_progress`, `implemented`, `not_used`, `deprecated` |
| `time_frame` | yes | `string`; may be set to a value like `7d`, `28d`, `all`, `none`. |
| `data_source` | yes | `string`; may be set to a value like `database`, `redis`, `redis_hll`, `prometheus`, `ruby`. |

View file

@ -90,7 +90,7 @@ Some start/end event pairs are not "compatible" with each other. For example:
- "Issue closed" to "Issue closed": Duration is always 0.
The `StageEvents` module describes the allowed `start_event` and `end_event` pairings (`PAIRING_RULES` constant). If a new event is added, it needs to be registered in this module.
To add a new event:
To add a new event:
1. Add an entry in `ENUM_MAPPING` with a unique number, which is used in the `Stage` model as `enum`.
1. Define which events are compatible with the event in the `PAIRING_RULES` hash.
@ -190,9 +190,9 @@ Currently supported parents:
### Default stages
The [original implementation](https://gitlab.com/gitlab-org/gitlab/-/issues/847) of value stream analytics defined 7 stages. These stages are always available for each parent, however altering these stages is not possible.
To make things efficient and reduce the number of records created, the default stages are expressed as in-memory objects (not persisted). When the user creates a custom stage for the first time, all the stages are persisted. This behavior is implemented in the value stream analytics service objects.
The reason for this was that we'd like to add the abilities to hide and order stages later on.
## Data Collector

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View file

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View file

@ -59,7 +59,7 @@ To filter results:
1. Select a parameter to filter by.
1. Select a value from the autocompleted results, or type to refine the results.
![Value stream analytics filter bar](img/vsa_filter_bar_v13.3.png "Active filter bar for value stream analytics")
![Value stream analytics filter bar](img/vsa_filter_bar_v13_3.png "Active filter bar for value stream analytics")
### Date ranges
@ -299,10 +299,59 @@ To create a value stream:
1. Navigate to your group's **Analytics > Value Stream**.
1. Click the Value stream dropdown and select **Create new Value Stream**
1. Fill in a name for the new Value Stream
- You can [customize the stages](#creating-a-value-stream-with-stages) as the `value_stream_analytics_extended_form` feature flag is enabled.
1. Click the **Create Value Stream** button.
![New value stream](img/new_value_stream_v13_3.png "Creating a new value stream")
#### Creating a value stream with stages
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55572) in GitLab 13.10.
> - It's [deployed behind a feature flag](../../feature_flags.md), enabled by default.
> - It's enabled on GitLab.com.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](../../../administration/feature_flags.md). **(FREE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
You can create value streams with stages, starting with a default or a blank template. You can
add stages as desired.
To create a value stream with stages:
1. Navigate to your group's **Analytics > Value Stream**.
1. Find and select the Value Stream dropdown. Select **Create new Value Stream**.
1. Select either **Create from default template** or **Create from no template**.
- Default stages in the value stream can be hidden or re-ordered
![Default stage actions](img/vsa_default_stage_v13_10.png "Default stage actions")
- New stages can be added by clicking the 'Add another stage' button
- The name, start and end events for the stage can be selected
![Custom stage actions](img/vsa_custom_stage_v13_10.png "Custom stage actions")
1. Select the **Create Value Stream** button to save the value stream.
![Extended create value stream form](img/extended_value_stream_form_v13_10.png "Extended create value stream form")
#### Enable or disable value stream with stages
Value streams with stages is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable it.
To enable it:
```ruby
# For the instance
Feature.enable(:value_stream_analytics_extended_form)
```
To disable it:
```ruby
# For the instance
Feature.disable(:value_stream_analytics_extended_form)
```
### Deleting a value stream
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221205) in GitLab 13.4.
@ -314,7 +363,7 @@ To delete a custom value stream:
1. Click the **Delete (name of value stream)**.
1. Click the **Delete** button to confirm.
![Delete value stream](img/delete_value_stream_v13.4.png "Deleting a custom value stream")
![Delete value stream](img/delete_value_stream_v13_4.png "Deleting a custom value stream")
## Days to completion chart

View file

@ -95,9 +95,11 @@ Runners log in to the Dependency Proxy automatically. To pull through
the Dependency Proxy, use the `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX`
[predefined CI/CD variable](../../../ci/variables/predefined_variables.md):
Example pulling the latest alpine image:
```yaml
# .gitlab-ci.yml
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:latest
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/alpine:latest
```
There are other additional predefined CI/CD variables you can also use:
@ -125,13 +127,20 @@ To store a Docker image in Dependency Proxy storage:
1. Go to your group's **Packages & Registries > Dependency Proxy**.
1. Copy the **Dependency Proxy URL**.
1. Use one of these commands. In these examples, the image is `alpine:latest`.
1. You can also pull images by digest to specify exactly which version of an image to pull.
- Add the URL to your [`.gitlab-ci.yml`](../../../ci/yaml/README.md#image) file:
- Pull an image by tag by adding the image to your [`.gitlab-ci.yml`](../../../ci/yaml/README.md#image) file:
```shell
image: gitlab.example.com/groupname/dependency_proxy/containers/alpine:latest
```
- Pull an image by digest by adding the image to your [`.gitlab-ci.yml`](../../../ci/yaml/README.md#image) file:
```shell
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/alpine@sha256:c9375e662992791e3f39e919b26f510e5254b42792519c180aad254e6b38f4dc
```
- Manually pull the Docker image:
```shell

View file

@ -137,6 +137,10 @@ module API
end
end
# rubocop: enable CodeReuse/ActiveRecord
def authorize_group_creation!
authorize! :create_group
end
end
resource :groups do
@ -169,7 +173,7 @@ module API
if parent_group
authorize! :create_subgroup, parent_group
else
authorize! :create_group
authorize_group_creation!
end
group = create_group

View file

@ -629,6 +629,9 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def usage_activity_by_stage_monitor(time_period)
# Calculate histogram only for overall as other time periods aren't available/useful here.
integrations_histogram = time_period.empty? ? histogram(::AlertManagement::HttpIntegration.active, :project_id, buckets: 1..100) : nil
{
clusters: distinct_count(::Clusters::Cluster.where(time_period), :user_id),
clusters_applications_prometheus: cluster_applications_user_distinct_count(::Clusters::Applications::Prometheus, time_period),
@ -638,8 +641,9 @@ module Gitlab
projects_with_tracing_enabled: distinct_count(::Project.with_tracing_enabled.where(time_period), :creator_id),
projects_with_error_tracking_enabled: distinct_count(::Project.with_enabled_error_tracking.where(time_period), :creator_id),
projects_with_incidents: distinct_count(::Issue.incident.where(time_period), :project_id),
projects_with_alert_incidents: distinct_count(::Issue.incident.with_alert_management_alerts.where(time_period), :project_id)
}
projects_with_alert_incidents: distinct_count(::Issue.incident.with_alert_management_alerts.where(time_period), :project_id),
projects_with_enabled_alert_integrations_histogram: integrations_histogram
}.compact
end
# rubocop: enable CodeReuse/ActiveRecord

View file

@ -39,10 +39,12 @@ module Gitlab
extend self
FALLBACK = -1
HISTOGRAM_FALLBACK = { '-1' => -1 }.freeze
DISTRIBUTED_HLL_FALLBACK = -2
ALL_TIME_TIME_FRAME_NAME = "all"
SEVEN_DAYS_TIME_FRAME_NAME = "7d"
TWENTY_EIGHT_DAYS_TIME_FRAME_NAME = "28d"
MAX_BUCKET_SIZE = 100
def count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil)
if batch
@ -87,6 +89,73 @@ module Gitlab
FALLBACK
end
# We don't support batching with histograms.
# Please avoid using this method on large tables.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/323949.
#
# rubocop: disable CodeReuse/ActiveRecord
def histogram(relation, column, buckets:, bucket_size: buckets.size)
# Using lambda to avoid exposing histogram specific methods
parameters_valid = lambda do
error_message =
if buckets.first == buckets.last
'Lower bucket bound cannot equal to upper bucket bound'
elsif bucket_size == 0
'Bucket size cannot be zero'
elsif bucket_size > MAX_BUCKET_SIZE
"Bucket size #{bucket_size} exceeds the limit of #{MAX_BUCKET_SIZE}"
end
return true unless error_message
exception = ArgumentError.new(error_message)
exception.set_backtrace(caller)
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(exception)
false
end
return HISTOGRAM_FALLBACK unless parameters_valid.call
count_grouped = relation.group(column).select(Arel.star.count.as('count_grouped'))
cte = Gitlab::SQL::CTE.new(:count_cte, count_grouped)
# For example, 9 segements gives 10 buckets
bucket_segments = bucket_size - 1
width_bucket = Arel::Nodes::NamedFunction
.new('WIDTH_BUCKET', [cte.table[:count_grouped], buckets.first, buckets.last, bucket_segments])
.as('buckets')
query = cte
.table
.project(width_bucket, cte.table[:count])
.group('buckets')
.order('buckets')
.with(cte.to_arel)
# Return the histogram as a Hash because buckets are unique.
relation
.connection
.exec_query(query.to_sql)
.rows
.to_h
# Keys are converted to strings in Usage Ping JSON
.stringify_keys
rescue ActiveRecord::StatementInvalid => e
Gitlab::AppJsonLogger.error(
event: 'histogram',
relation: relation.table_name,
operation: 'histogram',
operation_args: [column, buckets.first, buckets.last, bucket_segments],
query: query.to_sql,
message: e.message
)
HISTOGRAM_FALLBACK
end
# rubocop: enable CodeReuse/ActiveRecord
def add(*args)
return -1 if args.any?(&:negative?)

View file

@ -9637,6 +9637,15 @@ msgstr ""
msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
msgstr ""
msgid "DastProfiles|This profile is currently being used in a policy."
msgstr ""
msgid "DastProfiles|This scanner profile is currently being used by a policy. To make edits you must remove it from the active policy."
msgstr ""
msgid "DastProfiles|This site profile is currently being used by a policy. To make edits you must remove it from the active policy."
msgstr ""
msgid "DastProfiles|Turn on AJAX spider"
msgstr ""
@ -22501,6 +22510,9 @@ msgstr ""
msgid "Pipeline|Failed"
msgstr ""
msgid "Pipeline|In progress"
msgstr ""
msgid "Pipeline|Key"
msgstr ""

View file

@ -96,7 +96,7 @@
"deckar01-task_list": "^2.3.1",
"diff": "^3.4.0",
"document-register-element": "1.14.3",
"dompurify": "^2.2.6",
"dompurify": "^2.2.7",
"dropzone": "^4.2.0",
"editorconfig": "^0.15.3",
"emoji-regex": "^7.0.3",

View file

@ -14,9 +14,9 @@ module QA
end
def enable_throttles
check_element :throttle_unauthenticated_checkbox
check_element :throttle_authenticated_api_checkbox
check_element :throttle_authenticated_web_checkbox
check_element(:throttle_unauthenticated_checkbox)
check_element(:throttle_authenticated_api_checkbox)
check_element(:throttle_authenticated_web_checkbox)
end
def save_settings

View file

@ -19,7 +19,7 @@ module QA
private
def check_allow_requests_to_local_network_from_services_checkbox
check_element :allow_requests_from_services_checkbox
check_element(:allow_requests_from_services_checkbox)
end
def click_save_changes_button

View file

@ -12,7 +12,7 @@ module QA
end
def enable_performance_bar
click_element :enable_performance_bar_checkbox
check_element(:enable_performance_bar_checkbox)
Capybara.current_session.driver.browser.manage.add_cookie(name: 'perf_bar_enabled', value: 'true')
end

View file

@ -13,13 +13,13 @@ module QA
end
def require_admin_approval_after_user_signup
check_element :require_admin_approval_after_user_signup_checkbox
click_element :save_changes_button
check_element(:require_admin_approval_after_user_signup_checkbox)
click_element(:save_changes_button)
end
def disable_signups
uncheck_element :signup_enabled_checkbox
click_element :save_changes_button
uncheck_element(:signup_enabled_checkbox)
click_element(:save_changes_button)
end
end
end

View file

@ -133,9 +133,15 @@ module QA
end
def check_element(name)
if find_element(name, visible: false).checked?
QA::Runtime::Logger.debug("#{name} is already checked")
return
end
retry_until(sleep_interval: 1) do
find_element(name).set(true)
checked = find_element(name).checked?
find_element(name, visible: false).click
checked = find_element(name, visible: false).checked?
QA::Runtime::Logger.debug(checked ? "#{name} was checked" : "#{name} was not checked")
@ -144,10 +150,19 @@ module QA
end
def uncheck_element(name)
retry_until(sleep_interval: 1) do
find_element(name).set(false)
unless find_element(name, visible: false).checked?
QA::Runtime::Logger.debug("#{name} is already unchecked")
!find_element(name).checked?
return
end
retry_until(sleep_interval: 1) do
find_element(name, visible: false).click
unchecked = !find_element(name, visible: false).checked?
QA::Runtime::Logger.debug(unchecked ? "#{name} was unchecked" : "#{name} was not unchecked")
unchecked
end
end

View file

@ -54,57 +54,57 @@ module QA
end
def set_lfs_enabled
expand_content :permission_lfs_2fa_content
check_element :lfs_checkbox
click_element :save_permissions_changes_button
expand_content(:permission_lfs_2fa_content)
check_element(:lfs_checkbox)
click_element(:save_permissions_changes_button)
end
def set_lfs_disabled
expand_content :permission_lfs_2fa_content
uncheck_element :lfs_checkbox
click_element :save_permissions_changes_button
expand_content(:permission_lfs_2fa_content)
uncheck_element(:lfs_checkbox)
click_element(:save_permissions_changes_button)
end
def set_request_access_enabled
expand_content :permission_lfs_2fa_content
check_element :request_access_checkbox
click_element :save_permissions_changes_button
expand_content(:permission_lfs_2fa_content)
check_element(:request_access_checkbox)
click_element(:save_permissions_changes_button)
end
def set_request_access_disabled
expand_content :permission_lfs_2fa_content
uncheck_element :request_access_checkbox
click_element :save_permissions_changes_button
expand_content(:permission_lfs_2fa_content)
uncheck_element(:request_access_checkbox)
click_element(:save_permissions_changes_button)
end
def set_require_2fa_enabled
expand_content :permission_lfs_2fa_content
check_element :require_2fa_checkbox
click_element :save_permissions_changes_button
expand_content(:permission_lfs_2fa_content)
check_element(:require_2fa_checkbox)
click_element(:save_permissions_changes_button)
end
def set_require_2fa_disabled
expand_content :permission_lfs_2fa_content
uncheck_element :require_2fa_checkbox
click_element :save_permissions_changes_button
expand_content(:permission_lfs_2fa_content)
uncheck_element(:require_2fa_checkbox)
click_element(:save_permissions_changes_button)
end
def set_project_creation_level(value)
expand_content :permission_lfs_2fa_content
expand_content(:permission_lfs_2fa_content)
select_element(:project_creation_level_dropdown, value)
click_element :save_permissions_changes_button
click_element(:save_permissions_changes_button)
end
def toggle_request_access
expand_content :permission_lfs_2fa_content
expand_content(:permission_lfs_2fa_content)
if find_element(:request_access_checkbox).checked?
uncheck_element :request_access_checkbox
uncheck_element(:request_access_checkbox)
else
check_element :request_access_checkbox
check_element(:request_access_checkbox)
end
click_element :save_permissions_changes_button
click_element(:save_permissions_changes_button)
end
end
end

View file

@ -228,6 +228,7 @@ module QA
!find_element(:squash_checkbox).disabled?
end
# TODO: Fix workaround for data-qa-selector failure
click_element(:squash_checkbox)
end

View file

@ -68,7 +68,7 @@ module QA
end
def enable_initialize_with_readme
check_element :initialize_with_readme_checkbox
check_element(:initialize_with_readme_checkbox)
end
end
end

View file

@ -36,7 +36,7 @@ module QA
end
def uncheck_rbac!
uncheck_element :rbac_checkbox
uncheck_element(:rbac_checkbox)
end
end
end

View file

@ -11,8 +11,8 @@ module QA
end
def enable_autodevops
check_element :enable_autodevops_checkbox
click_element :save_changes_button
check_element(:enable_autodevops_checkbox)
click_element(:save_changes_button)
end
end
end

View file

@ -10,7 +10,6 @@ module QA
view 'app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue' do
element :ci_variable_key_field
element :ci_variable_value_field
element :ci_variable_masked_checkbox
element :ci_variable_save_button
element :ci_variable_delete_button
end

View file

@ -13,7 +13,7 @@ module QA
end
def enable_issues_for_incidents
check_element :create_issue_checkbox
check_element(:create_issue_checkbox)
end
def select_issue_template(template)

View file

@ -29,7 +29,7 @@ module QA
end
def enable_merge_if_all_disscussions_are_resolved
click_element :allow_merge_if_all_discussions_are_resolved_checkbox
check_element(:allow_merge_if_all_discussions_are_resolved_checkbox)
click_save_changes
end
end

View file

@ -44,10 +44,6 @@ module QA
element :commit_button
end
view 'app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue' do
element :start_new_mr_checkbox
end
view 'app/assets/javascripts/ide/components/repo_editor.vue' do
element :editor_container
end

View file

@ -34,6 +34,7 @@ UsageData/LargeTable:
CountMethods:
- :count
- :distinct_count
- :histogram
AllowedMethods:
- :arel_table
- :minimum

View file

@ -30,13 +30,13 @@ then
((ERRORCODE++))
fi
# Test for non-standard spaces (NBSP, NNBSP) in documentation.
# Test for non-standard spaces (NBSP, NNBSP, ZWSP) in documentation.
echo '=> Checking for non-standard spaces...'
echo
grep --extended-regexp --binary-file=without-match --recursive '[ ]' doc/ >/dev/null 2>&1
grep --extended-regexp --binary-file=without-match --recursive '[ ]' doc/ >/dev/null 2>&1
if [ $? -eq 0 ]
then
echo '✖ ERROR: Non-standard spaces (NBSP, NNBSP) should not be used in documentation.
echo '✖ ERROR: Non-standard spaces (NBSP, NNBSP, ZWSP) should not be used in documentation.
https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#spaces-between-words
Replace with standard spaces:' >&2
# Find the spaces, then add color codes with sed to highlight each NBSP or NNBSP in the output.

View file

@ -250,8 +250,8 @@ describe('Getters PackageDetails Store', () => {
setupState();
expect(gradleGroovyAddSourceCommand(state)).toMatchInlineSnapshot(`
"gitlab {
url \\"foo/registry\\"
"maven {
url 'foo/registry'
}"
`);
});

View file

@ -116,7 +116,7 @@ describe('Linked Pipelines Column', () => {
it('emits the error', async () => {
await clickExpandButton();
expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
expect(wrapper.emitted().error).toEqual([[{ type: LOAD_FAILURE, skipSentry: true }]]);
});
it('does not show the pipeline', async () => {
@ -167,7 +167,7 @@ describe('Linked Pipelines Column', () => {
it('emits the error', async () => {
await clickExpandButton();
expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
expect(wrapper.emitted().error).toEqual([[{ type: LOAD_FAILURE, skipSentry: true }]]);
});
it('does not show the pipeline', async () => {

View file

@ -29,6 +29,7 @@ describe('Timeago component', () => {
const duration = () => wrapper.find('.duration');
const finishedAt = () => wrapper.find('.finished-at');
const findInProgress = () => wrapper.find('[data-testid="pipeline-in-progress"]');
describe('with duration', () => {
beforeEach(() => {
@ -77,4 +78,21 @@ describe('Timeago component', () => {
expect(finishedAt().exists()).toBe(false);
});
});
describe('in progress', () => {
it.each`
durationTime | finishedAtTime | shouldShow
${10} | ${'2017-04-26T12:40:23.277Z'} | ${false}
${10} | ${''} | ${false}
${0} | ${'2017-04-26T12:40:23.277Z'} | ${false}
${0} | ${''} | ${true}
`(
'progress state shown: $shouldShow when pipeline duration is $durationTime and finished_at is $finishedAtTime',
({ durationTime, finishedAtTime, shouldShow }) => {
createComponent({ duration: durationTime, finished_at: finishedAtTime });
expect(findInProgress().exists()).toBe(shouldShow);
},
);
});
});

View file

@ -1,85 +1,88 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import relatedLinksComponent from '~/vue_merge_request_widget/components/mr_widget_related_links.vue';
import { shallowMount } from '@vue/test-utils';
import RelatedLinks from '~/vue_merge_request_widget/components/mr_widget_related_links.vue';
describe('MRWidgetRelatedLinks', () => {
let vm;
let wrapper;
const createComponent = (data) => {
const Component = Vue.extend(relatedLinksComponent);
return mountComponent(Component, data);
const createComponent = (propsData = {}) => {
wrapper = shallowMount(RelatedLinks, { propsData });
};
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
describe('computed', () => {
describe('closesText', () => {
it('returns Closes text for open merge request', () => {
vm = createComponent({ state: 'open', relatedLinks: {} });
createComponent({ state: 'open', relatedLinks: {} });
expect(vm.closesText).toEqual('Closes');
expect(wrapper.vm.closesText).toBe('Closes');
});
it('returns correct text for closed merge request', () => {
vm = createComponent({ state: 'closed', relatedLinks: {} });
createComponent({ state: 'closed', relatedLinks: {} });
expect(vm.closesText).toEqual('Did not close');
expect(wrapper.vm.closesText).toBe('Did not close');
});
it('returns correct tense for merged request', () => {
vm = createComponent({ state: 'merged', relatedLinks: {} });
createComponent({ state: 'merged', relatedLinks: {} });
expect(vm.closesText).toEqual('Closed');
expect(wrapper.vm.closesText).toBe('Closed');
});
});
});
it('should have only have closing issues text', () => {
vm = createComponent({
createComponent({
relatedLinks: {
closing: '<a href="#">#23</a> and <a>#42</a>',
},
});
const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
const content = wrapper
.text()
.replace(/\n(\s)+/g, ' ')
.trim();
expect(content).toContain('Closes #23 and #42');
expect(content).not.toContain('Mentions');
});
it('should have only have mentioned issues text', () => {
vm = createComponent({
createComponent({
relatedLinks: {
mentioned: '<a href="#">#7</a>',
},
});
expect(vm.$el.innerText).toContain('Mentions #7');
expect(vm.$el.innerText).not.toContain('Closes');
expect(wrapper.text().trim()).toContain('Mentions #7');
expect(wrapper.text().trim()).not.toContain('Closes');
});
it('should have closing and mentioned issues at the same time', () => {
vm = createComponent({
createComponent({
relatedLinks: {
closing: '<a href="#">#7</a>',
mentioned: '<a href="#">#23</a> and <a>#42</a>',
},
});
const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
const content = wrapper
.text()
.replace(/\n(\s)+/g, ' ')
.trim();
expect(content).toContain('Closes #7');
expect(content).toContain('Mentions #23 and #42');
});
it('should have assing issues link', () => {
vm = createComponent({
createComponent({
relatedLinks: {
assignToMe: '<a href="#">Assign yourself to these issues</a>',
},
});
expect(vm.$el.innerText).toContain('Assign yourself to these issues');
expect(wrapper.text().trim()).toContain('Assign yourself to these issues');
});
});

View file

@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe BoardsHelper do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:base_group) { create(:group, path: 'base') }
let_it_be(:project) { create(:project, group: base_group) }
let_it_be(:project_board) { create(:board, project: project) }
let_it_be(:group_board) { create(:board, group: base_group) }
@ -82,6 +82,10 @@ RSpec.describe BoardsHelper do
expect(helper.board_data[:labels_fetch_path]).to eq("/#{project.full_path}/-/labels.json?include_ancestor_groups=true")
expect(helper.board_data[:labels_manage_path]).to eq("/#{project.full_path}/-/labels")
end
it 'returns the group id of a project' do
expect(helper.board_data[:group_id]).to eq(project.group.id)
end
end
context 'group board' do
@ -102,6 +106,10 @@ RSpec.describe BoardsHelper do
expect(helper.board_data[:labels_fetch_path]).to eq("/groups/#{base_group.full_path}/-/labels.json?include_ancestor_groups=true&only_group_labels=true")
expect(helper.board_data[:labels_manage_path]).to eq("/groups/#{base_group.full_path}/-/labels")
end
it 'returns the group id' do
expect(helper.board_data[:group_id]).to eq(base_group.id)
end
end
end

View file

@ -382,14 +382,15 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
describe 'usage_activity_by_stage_monitor' do
it 'includes accurate usage_activity_by_stage data' do
for_defined_days_back do
user = create(:user, dashboard: 'operations')
user = create(:user, dashboard: 'operations')
cluster = create(:cluster, user: user)
create(:project, creator: user)
project = create(:project, creator: user)
create(:clusters_applications_prometheus, :installed, cluster: cluster)
create(:project_tracing_setting)
create(:project_error_tracking_setting)
create(:incident)
create(:incident, alert_management_alert: create(:alert_management_alert))
create(:alert_management_http_integration, :active, project: project)
end
expect(described_class.usage_activity_by_stage_monitor({})).to include(
@ -399,10 +400,12 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
projects_with_tracing_enabled: 2,
projects_with_error_tracking_enabled: 2,
projects_with_incidents: 4,
projects_with_alert_incidents: 2
projects_with_alert_incidents: 2,
projects_with_enabled_alert_integrations_histogram: { '1' => 2 }
)
expect(described_class.usage_activity_by_stage_monitor(described_class.last_28_days_time_period)).to include(
data_28_days = described_class.usage_activity_by_stage_monitor(described_class.last_28_days_time_period)
expect(data_28_days).to include(
clusters: 1,
clusters_applications_prometheus: 1,
operations_dashboard_default_dashboard: 1,
@ -411,6 +414,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
projects_with_incidents: 2,
projects_with_alert_incidents: 1
)
expect(data_28_days).not_to include(:projects_with_enabled_alert_integrations_histogram)
end
end
@ -528,14 +533,14 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(subject.keys).to include(*UsageDataHelpers::USAGE_DATA_KEYS)
end
it 'gathers usage counts' do
it 'gathers usage counts', :aggregate_failures do
count_data = subject[:counts]
expect(count_data[:boards]).to eq(1)
expect(count_data[:projects]).to eq(4)
expect(count_data.values_at(*UsageDataHelpers::SMAU_KEYS)).to all(be_an(Integer))
expect(count_data.keys).to include(*UsageDataHelpers::COUNTS_KEYS)
expect(UsageDataHelpers::COUNTS_KEYS - count_data.keys).to be_empty
expect(count_data.values).to all(be_a_kind_of(Integer))
end
it 'gathers usage counts correctly' do

View file

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Utils::UsageData do
include Database::DatabaseHelpers
describe '#count' do
let(:relation) { double(:relation) }
@ -183,6 +185,102 @@ RSpec.describe Gitlab::Utils::UsageData do
end
end
describe '#histogram' do
let_it_be(:projects) { create_list(:project, 3) }
let(:project1) { projects.first }
let(:project2) { projects.second }
let(:project3) { projects.third }
let(:fallback) { described_class::HISTOGRAM_FALLBACK }
let(:relation) { AlertManagement::HttpIntegration.active }
let(:column) { :project_id }
def expect_error(exception, message, &block)
expect(Gitlab::ErrorTracking)
.to receive(:track_and_raise_for_dev_exception)
.with(instance_of(exception))
.and_call_original
expect(&block).to raise_error(
an_instance_of(exception).and(
having_attributes(message: message, backtrace: be_kind_of(Array)))
)
end
it 'checks bucket bounds to be not equal' do
expect_error(ArgumentError, 'Lower bucket bound cannot equal to upper bucket bound') do
described_class.histogram(relation, column, buckets: 1..1)
end
end
it 'checks bucket_size being non-zero' do
expect_error(ArgumentError, 'Bucket size cannot be zero') do
described_class.histogram(relation, column, buckets: 1..2, bucket_size: 0)
end
end
it 'limits the amount of buckets without providing bucket_size argument' do
expect_error(ArgumentError, 'Bucket size 101 exceeds the limit of 100') do
described_class.histogram(relation, column, buckets: 1..101)
end
end
it 'limits the amount of buckets when providing bucket_size argument' do
expect_error(ArgumentError, 'Bucket size 101 exceeds the limit of 100') do
described_class.histogram(relation, column, buckets: 1..2, bucket_size: 101)
end
end
it 'without data' do
histogram = described_class.histogram(relation, column, buckets: 1..100)
expect(histogram).to eq({})
end
it 'aggregates properly within bounds' do
create(:alert_management_http_integration, :active, project: project1)
create(:alert_management_http_integration, :inactive, project: project1)
create(:alert_management_http_integration, :active, project: project2)
create(:alert_management_http_integration, :active, project: project2)
create(:alert_management_http_integration, :inactive, project: project2)
create(:alert_management_http_integration, :active, project: project3)
create(:alert_management_http_integration, :inactive, project: project3)
histogram = described_class.histogram(relation, column, buckets: 1..100)
expect(histogram).to eq('1' => 2, '2' => 1)
end
it 'aggregates properly out of bounds' do
create_list(:alert_management_http_integration, 3, :active, project: project1)
histogram = described_class.histogram(relation, column, buckets: 1..2)
expect(histogram).to eq('2' => 1)
end
it 'returns fallback and logs canceled queries' do
create(:alert_management_http_integration, :active, project: project1)
expect(Gitlab::AppJsonLogger).to receive(:error).with(
event: 'histogram',
relation: relation.table_name,
operation: 'histogram',
operation_args: [column, 1, 100, 99],
query: kind_of(String),
message: /PG::QueryCanceled/
)
with_statement_timeout(0.001) do
relation = AlertManagement::HttpIntegration.select('pg_sleep(0.002)')
histogram = described_class.histogram(relation, column, buckets: 1..100)
expect(histogram).to eq(fallback)
end
end
end
describe '#add' do
it 'adds given values' do
expect(described_class.add(1, 3)).to eq(4)

View file

@ -5,11 +5,65 @@ module Database
# In order to directly work with views using factories,
# we can swapout the view for a table of identical structure.
def swapout_view_for_table(view)
ActiveRecord::Base.connection.execute(<<~SQL)
ActiveRecord::Base.connection.execute(<<~SQL.squish)
CREATE TABLE #{view}_copy (LIKE #{view});
DROP VIEW #{view};
ALTER TABLE #{view}_copy RENAME TO #{view};
SQL
end
# Set statement timeout temporarily.
# Useful when testing query timeouts.
#
# Note that this method cannot restore the timeout if a query
# was canceled due to e.g. a statement timeout.
# Refrain from using this transaction in these situations.
#
# @param timeout - Statement timeout in seconds
#
# Example:
#
# with_statement_timeout(0.1) do
# model.select('pg_sleep(0.11)')
# end
def with_statement_timeout(timeout)
# Force a positive value and a minimum of 1ms for very small values.
timeout = (timeout * 1000).abs.ceil
raise ArgumentError, 'Using a timeout of `0` means to disable statement timeout.' if timeout == 0
previous_timeout = ActiveRecord::Base.connection
.exec_query('SHOW statement_timeout')[0].fetch('statement_timeout')
set_statement_timeout("#{timeout}ms")
yield
ensure
begin
set_statement_timeout(previous_timeout)
rescue ActiveRecord::StatementInvalid
# After a transaction was canceled/aborted due to e.g. a statement
# timeout commands are ignored and will raise in PG::InFailedSqlTransaction.
# We can safely ignore this error because the statement timeout was set
# for the currrent transaction which will be closed anyway.
end
end
# Set statement timeout for the current transaction.
#
# Note, that it does not restore the previous statement timeout.
# Use `with_statement_timeout` instead.
#
# @param timeout - Statement timeout in seconds
#
# Example:
#
# set_statement_timeout(0.1)
# model.select('pg_sleep(0.11)')
def set_statement_timeout(timeout)
ActiveRecord::Base.connection.execute(
format(%(SET LOCAL statement_timeout = '%s'), timeout)
)
end
end
end

View file

@ -4308,10 +4308,10 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"
dompurify@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.6.tgz#54945dc5c0b45ce5ae228705777e8e59d7b2edc4"
integrity sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ==
dompurify@^2.2.6, dompurify@^2.2.7:
version "2.2.7"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.7.tgz#a5f055a2a471638680e779bd08fc334962d11fd8"
integrity sha512-jdtDffdGNY+C76jvodNTu9jt5yYj59vuTUyx+wXdzcSwAGTYZDAQkQ7Iwx9zcGrA4ixC1syU4H3RZROqRxokxg==
domutils@^1.5.1:
version "1.7.0"