Add latest changes from gitlab-org/gitlab@master
|
@ -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') }}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -4,6 +4,7 @@ fragment EpicNode on Epic {
|
|||
title
|
||||
state
|
||||
reference
|
||||
webPath
|
||||
webUrl
|
||||
createdAt
|
||||
closedAt
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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', () => {
|
|||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')} }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Usage ping: Histogram for enabled integrations per project'
|
||||
merge_request: 55782
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/btn-confirm-project-milestones.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move from btn-success to btn-confirm in milestones directory
|
||||
merge_request: 56342
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
5
changelogs/unreleased/btn-confirm-project-mirrors.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move from btn-success to btn-confirm in mirrors directory
|
||||
merge_request: 56343
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
5
changelogs/unreleased/cromefire_-master-patch-00495.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Correct generated maven repository instruction for Gradle Groovy DSL
|
||||
merge_request: 56318
|
||||
author: Cromefire_ (@cromefire_)
|
||||
type: fixed
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Display in progress for pipeline duration cell when pipeline has not finished running
|
||||
merge_request: 56266
|
||||
author:
|
||||
type: changed
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -22,7 +22,7 @@
|
|||
},
|
||||
"value_type": {
|
||||
"type": "string",
|
||||
"enum": ["string", "number", "boolean"]
|
||||
"enum": ["string", "number", "boolean", "object"]
|
||||
},
|
||||
"status": {
|
||||
"type": ["string"],
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`. |
|
||||
|
|
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 253 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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?)
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -36,7 +36,7 @@ module QA
|
|||
end
|
||||
|
||||
def uncheck_rbac!
|
||||
uncheck_element :rbac_checkbox
|
||||
uncheck_element(:rbac_checkbox)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,6 +34,7 @@ UsageData/LargeTable:
|
|||
CountMethods:
|
||||
- :count
|
||||
- :distinct_count
|
||||
- :histogram
|
||||
AllowedMethods:
|
||||
- :arel_table
|
||||
- :minimum
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -250,8 +250,8 @@ describe('Getters PackageDetails Store', () => {
|
|||
setupState();
|
||||
|
||||
expect(gradleGroovyAddSourceCommand(state)).toMatchInlineSnapshot(`
|
||||
"gitlab {
|
||||
url \\"foo/registry\\"
|
||||
"maven {
|
||||
url 'foo/registry'
|
||||
}"
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -384,12 +384,13 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
for_defined_days_back do
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|