Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-01-22 18:10:33 +00:00
parent 917d93d86d
commit a9a2f9257e
92 changed files with 892 additions and 914 deletions

View File

@ -24,7 +24,7 @@ _Why do you believe this AI solution is a good way to solve this problem?_
_What assumptions are you making about this problem and the solution?_
### Personas
_What [personas](https://about.gitlab.com/handbook/product/personas/#list-of-user-personas) have this problem, who is the intended user?_
_What [personas](https://handbook.gitlab.com/handbook/product/personas/#list-of-user-personas) have this problem, who is the intended user?_
## Proposal
<!-- Explain the proposed changes, including details around usage and business drivers. -->

View File

@ -95,25 +95,25 @@ If you enjoy taking notes using Post-it notes make sure you have some of them as
Deciding which persona we are focusing on will be part of the Day 1 discussions in the workshop. The personas we are going to consider are:
<!-- Choose which personas could be target users, and choose from this list during the Sprint. Personas are described at https://about.gitlab.com/handbook/product/personas/
<!-- Choose which personas could be target users, and choose from this list during the Sprint. Personas are described at https://handbook.gitlab.com/handbook/product/personas/
* [Parker (Product Manager)](https://about.gitlab.com/handbook/product/personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://about.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://about.gitlab.com/handbook/product/personas/#presley-product-designer)
* [Sasha (Software Developer)](https://about.gitlab.com/handbook/product/personas/#sasha-software-developer)
* [Priyanka (Platform Engineer)](https://about.gitlab.com/handbook/product/personas/#priyanka-platform-engineer)
* [Sidney (Systems Administrator)](https://about.gitlab.com/handbook/product/personas/#sidney-systems-administrator)
* [Rachel (Release Manager)](https://about.gitlab.com/handbook/product/personas/#rachel-release-manager)
* [Simone (Software Engineer in Test)](https://about.gitlab.com/handbook/product/personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://about.gitlab.com/handbook/product/personas/#allison-application-ops)
* [Ingrid (Infrastructure Operator)](https://about.gitlab.com/handbook/product/personas/#ingrid-infrastructure-operator)
* [Dakota (Application Development Director)](https://about.gitlab.com/handbook/product/personas/#dakota-application-development-director)
* [Dana (Data Analyst)](https://about.gitlab.com/handbook/product/personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://about.gitlab.com/handbook/product/personas/#eddie-content-editor)
* [Amy (Application Security Engineer)](https://about.gitlab.com/handbook/product/personas/#amy-application-security-engineer)
* [Isaac (Infrastructure Engineer)](https://about.gitlab.com/handbook/product/personas/#isaac-infrastructure-security-engineer)
* [Alex (Security Operations Engineer)](https://about.gitlab.com/handbook/product/personas/#alex-security-operations-engineer)
* [Cameron (Compliance Manager)](https://about.gitlab.com/handbook/product/personas/#cameron-compliance-manager)
* [Parker (Product Manager)](https://handbook.gitlab.com/handbook/product/personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://handbook.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://handbook.gitlab.com/handbook/product/personas/#presley-product-designer)
* [Sasha (Software Developer)](https://handbook.gitlab.com/handbook/product/personas/#sasha-software-developer)
* [Priyanka (Platform Engineer)](https://handbook.gitlab.com/handbook/product/personas/#priyanka-platform-engineer)
* [Sidney (Systems Administrator)](https://handbook.gitlab.com/handbook/product/personas/#sidney-systems-administrator)
* [Rachel (Release Manager)](https://handbook.gitlab.com/handbook/product/personas/#rachel-release-manager)
* [Simone (Software Engineer in Test)](https://handbook.gitlab.com/handbook/product/personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://handbook.gitlab.com/handbook/product/personas/#allison-application-ops)
* [Ingrid (Infrastructure Operator)](https://handbook.gitlab.com/handbook/product/personas/#ingrid-infrastructure-operator)
* [Dakota (Application Development Director)](https://handbook.gitlab.com/handbook/product/personas/#dakota-application-development-director)
* [Dana (Data Analyst)](https://handbook.gitlab.com/handbook/product/personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://handbook.gitlab.com/handbook/product/personas/#eddie-content-editor)
* [Amy (Application Security Engineer)](https://handbook.gitlab.com/handbook/product/personas/#amy-application-security-engineer)
* [Isaac (Infrastructure Engineer)](https://handbook.gitlab.com/handbook/product/personas/#isaac-infrastructure-security-engineer)
* [Alex (Security Operations Engineer)](https://handbook.gitlab.com/handbook/product/personas/#alex-security-operations-engineer)
* [Cameron (Compliance Manager)](https://handbook.gitlab.com/handbook/product/personas/#cameron-compliance-manager)
-->

View File

@ -24,7 +24,7 @@
* Any concepts, procedures, reference info we could add to make it easier to successfully use GitLab?
* Include use cases, benefits, and/or goals for this work.
* If adding content: What audience is it intended for? (What roles and scenarios?)
For ideas, see personas at https://about.gitlab.com/handbook/product/personas/ or the persona labels at
For ideas, see personas at https://handbook.gitlab.com/handbook/product/personas/ or the persona labels at
https://gitlab.com/groups/gitlab-org/-/labels?subscribed=&search=persona%3A
-->

View File

@ -18,25 +18,25 @@ The goal of this template is brevity for quick/smaller iterations. For a more th
<!-- Who will use this feature? If known, include any of the following: types of users (e.g. Developer), personas, or specific company roles (e.g. Release Manager). It's okay to write "Unknown" and fill this field in later.
Personas are described at https://about.gitlab.com/handbook/product/personas/
Personas are described at https://handbook.gitlab.com/handbook/product/personas/
* [Parker (Product Manager)](https://about.gitlab.com/handbook/product/personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://about.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://about.gitlab.com/handbook/product/personas/#presley-product-designer)
* [Sasha (Software Developer)](https://about.gitlab.com/handbook/product/personas/#sasha-software-developer)
* [Priyanka (Platform Engineer)](https://about.gitlab.com/handbook/product/personas/#priyanka-platform-engineer)
* [Sidney (Systems Administrator)](https://about.gitlab.com/handbook/product/personas/#sidney-systems-administrator)
* [Rachel (Release Manager)](https://about.gitlab.com/handbook/product/personas/#rachel-release-manager)
* [Simone (Software Engineer in Test)](https://about.gitlab.com/handbook/product/personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://about.gitlab.com/handbook/product/personas/#allison-application-ops)
* [Ingrid (Infrastructure Operator)](https://about.gitlab.com/handbook/product/personas/#ingrid-infrastructure-operator)
* [Dakota (Application Development Director)](https://about.gitlab.com/handbook/product/personas/#dakota-application-development-director)
* [Dana (Data Analyst)](https://about.gitlab.com/handbook/product/personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://about.gitlab.com/handbook/product/personas/#eddie-content-editor)
* [Amy (Application Security Engineer)](https://about.gitlab.com/handbook/product/personas/#amy-application-security-engineer)
* [Isaac (Infrastructure Engineer)](https://about.gitlab.com/handbook/product/personas/#isaac-infrastructure-security-engineer)
* [Alex (Security Operations Engineer)](https://about.gitlab.com/handbook/product/personas/#alex-security-operations-engineer)
* [Cameron (Compliance Manager)](https://about.gitlab.com/handbook/product/personas/#cameron-compliance-manager)
* [Parker (Product Manager)](https://handbook.gitlab.com/handbook/product/personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://handbook.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://handbook.gitlab.com/handbook/product/personas/#presley-product-designer)
* [Sasha (Software Developer)](https://handbook.gitlab.com/handbook/product/personas/#sasha-software-developer)
* [Priyanka (Platform Engineer)](https://handbook.gitlab.com/handbook/product/personas/#priyanka-platform-engineer)
* [Sidney (Systems Administrator)](https://handbook.gitlab.com/handbook/product/personas/#sidney-systems-administrator)
* [Rachel (Release Manager)](https://handbook.gitlab.com/handbook/product/personas/#rachel-release-manager)
* [Simone (Software Engineer in Test)](https://handbook.gitlab.com/handbook/product/personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://handbook.gitlab.com/handbook/product/personas/#allison-application-ops)
* [Ingrid (Infrastructure Operator)](https://handbook.gitlab.com/handbook/product/personas/#ingrid-infrastructure-operator)
* [Dakota (Application Development Director)](https://handbook.gitlab.com/handbook/product/personas/#dakota-application-development-director)
* [Dana (Data Analyst)](https://handbook.gitlab.com/handbook/product/personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://handbook.gitlab.com/handbook/product/personas/#eddie-content-editor)
* [Amy (Application Security Engineer)](https://handbook.gitlab.com/handbook/product/personas/#amy-application-security-engineer)
* [Isaac (Infrastructure Engineer)](https://handbook.gitlab.com/handbook/product/personas/#isaac-infrastructure-security-engineer)
* [Alex (Security Operations Engineer)](https://handbook.gitlab.com/handbook/product/personas/#alex-security-operations-engineer)
* [Cameron (Compliance Manager)](https://handbook.gitlab.com/handbook/product/personas/#cameron-compliance-manager)
-->
### Feature Usage Metrics

View File

@ -12,25 +12,25 @@
<!-- Who will use this feature? If known, include any of the following: types of users (e.g. Developer), personas, or specific company roles (e.g. Release Manager). It's okay to write "Unknown" and fill this field in later.
Personas are described at https://about.gitlab.com/handbook/product/personas/
Personas are described at https://handbook.gitlab.com/handbook/product/personas/
* [Parker (Product Manager)](https://about.gitlab.com/handbook/product/personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://about.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://about.gitlab.com/handbook/product/personas/#presley-product-designer)
* [Sasha (Software Developer)](https://about.gitlab.com/handbook/product/personas/#sasha-software-developer)
* [Priyanka (Platform Engineer)](https://about.gitlab.com/handbook/product/personas/#priyanka-platform-engineer)
* [Sidney (Systems Administrator)](https://about.gitlab.com/handbook/product/personas/#sidney-systems-administrator)
* [Rachel (Release Manager)](https://about.gitlab.com/handbook/product/personas/#rachel-release-manager)
* [Simone (Software Engineer in Test)](https://about.gitlab.com/handbook/product/personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://about.gitlab.com/handbook/product/personas/#allison-application-ops)
* [Ingrid (Infrastructure Operator)](https://about.gitlab.com/handbook/product/personas/#ingrid-infrastructure-operator)
* [Dakota (Application Development Director)](https://about.gitlab.com/handbook/product/personas/#dakota-application-development-director)
* [Dana (Data Analyst)](https://about.gitlab.com/handbook/product/personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://about.gitlab.com/handbook/product/personas/#eddie-content-editor)
* [Amy (Application Security Engineer)](https://about.gitlab.com/handbook/product/personas/#amy-application-security-engineer)
* [Isaac (Infrastructure Engineer)](https://about.gitlab.com/handbook/product/personas/#isaac-infrastructure-security-engineer)
* [Alex (Security Operations Engineer)](https://about.gitlab.com/handbook/product/personas/#alex-security-operations-engineer)
* [Cameron (Compliance Manager)](https://about.gitlab.com/handbook/product/personas/#cameron-compliance-manager)
* [Parker (Product Manager)](https://handbook.gitlab.com/handbook/product/personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://handbook.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://handbook.gitlab.com/handbook/product/personas/#presley-product-designer)
* [Sasha (Software Developer)](https://handbook.gitlab.com/handbook/product/personas/#sasha-software-developer)
* [Priyanka (Platform Engineer)](https://handbook.gitlab.com/handbook/product/personas/#priyanka-platform-engineer)
* [Sidney (Systems Administrator)](https://handbook.gitlab.com/handbook/product/personas/#sidney-systems-administrator)
* [Rachel (Release Manager)](https://handbook.gitlab.com/handbook/product/personas/#rachel-release-manager)
* [Simone (Software Engineer in Test)](https://handbook.gitlab.com/handbook/product/personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://handbook.gitlab.com/handbook/product/personas/#allison-application-ops)
* [Ingrid (Infrastructure Operator)](https://handbook.gitlab.com/handbook/product/personas/#ingrid-infrastructure-operator)
* [Dakota (Application Development Director)](https://handbook.gitlab.com/handbook/product/personas/#dakota-application-development-director)
* [Dana (Data Analyst)](https://handbook.gitlab.com/handbook/product/personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://handbook.gitlab.com/handbook/product/personas/#eddie-content-editor)
* [Amy (Application Security Engineer)](https://handbook.gitlab.com/handbook/product/personas/#amy-application-security-engineer)
* [Isaac (Infrastructure Engineer)](https://handbook.gitlab.com/handbook/product/personas/#isaac-infrastructure-security-engineer)
* [Alex (Security Operations Engineer)](https://handbook.gitlab.com/handbook/product/personas/#alex-security-operations-engineer)
* [Cameron (Compliance Manager)](https://handbook.gitlab.com/handbook/product/personas/#cameron-compliance-manager)
-->
@ -110,7 +110,7 @@ Explore (../../doc/development/internal_analytics/internal_event_instrumentation
### What is the type of buyer?
<!-- What is the buyer persona for this feature? See https://about.gitlab.com/handbook/product/personas/buyer-persona/
<!-- What is the buyer persona for this feature? See https://handbook.gitlab.com/handbook/product/personas/buyer-persona/
In which enterprise tier should this feature go? See https://about.gitlab.com/handbook/product/pricing/#three-tiers -->
### Is this a cross-stage feature?

View File

@ -6,25 +6,25 @@ The goal of this template is to ensure we have captured all the information avai
<!-- If known, include any of the following: types of users (e.g. Developer), personas, or specific company roles (e.g. Release Manager). It's okay to write "Unknown" and fill this field in later.
Personas are described at https://about.gitlab.com/handbook/product/personas/
Personas are described at https://handbook.gitlab.com/handbook/product/personas/
* [Parker (Product Manager)](https://about.gitlab.com/handbook/product/personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://about.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://about.gitlab.com/handbook/product/personas/#presley-product-designer)
* [Sasha (Software Developer)](https://about.gitlab.com/handbook/product/personas/#sasha-software-developer)
* [Priyanka (Platform Engineer)](https://about.gitlab.com/handbook/product/personas/#priyanka-platform-engineer)
* [Sidney (Systems Administrator)](https://about.gitlab.com/handbook/product/personas/#sidney-systems-administrator)
* [Rachel (Release Manager)](https://about.gitlab.com/handbook/product/personas/#rachel-release-manager)
* [Simone (Software Engineer in Test)](https://about.gitlab.com/handbook/product/personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://about.gitlab.com/handbook/product/personas/#allison-application-ops)
* [Ingrid (Infrastructure Operator)](https://about.gitlab.com/handbook/product/personas/#ingrid-infrastructure-operator)
* [Dakota (Application Development Director)](https://about.gitlab.com/handbook/product/personas/#dakota-application-development-director)
* [Dana (Data Analyst)](https://about.gitlab.com/handbook/product/personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://about.gitlab.com/handbook/product/personas/#eddie-content-editor)
* [Amy (Application Security Engineer)](https://about.gitlab.com/handbook/product/personas/#amy-application-security-engineer)
* [Isaac (Infrastructure Engineer)](https://about.gitlab.com/handbook/product/personas/#isaac-infrastructure-security-engineer)
* [Alex (Security Operations Engineer)](https://about.gitlab.com/handbook/product/personas/#alex-security-operations-engineer)
* [Cameron (Compliance Manager)](https://about.gitlab.com/handbook/product/personas/#cameron-compliance-manager)
* [Parker (Product Manager)](https://handbook.gitlab.com/handbook/product/personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://handbook.gitlab.com/handbook/product/personas/#delaney-development-team-lead)
* [Presley (Product Designer)](https://handbook.gitlab.com/handbook/product/personas/#presley-product-designer)
* [Sasha (Software Developer)](https://handbook.gitlab.com/handbook/product/personas/#sasha-software-developer)
* [Priyanka (Platform Engineer)](https://handbook.gitlab.com/handbook/product/personas/#priyanka-platform-engineer)
* [Sidney (Systems Administrator)](https://handbook.gitlab.com/handbook/product/personas/#sidney-systems-administrator)
* [Rachel (Release Manager)](https://handbook.gitlab.com/handbook/product/personas/#rachel-release-manager)
* [Simone (Software Engineer in Test)](https://handbook.gitlab.com/handbook/product/personas/#simone-software-engineer-in-test)
* [Allison (Application Ops)](https://handbook.gitlab.com/handbook/product/personas/#allison-application-ops)
* [Ingrid (Infrastructure Operator)](https://handbook.gitlab.com/handbook/product/personas/#ingrid-infrastructure-operator)
* [Dakota (Application Development Director)](https://handbook.gitlab.com/handbook/product/personas/#dakota-application-development-director)
* [Dana (Data Analyst)](https://handbook.gitlab.com/handbook/product/personas/#dana-data-analyst)
* [Eddie (Content Editor)](https://handbook.gitlab.com/handbook/product/personas/#eddie-content-editor)
* [Amy (Application Security Engineer)](https://handbook.gitlab.com/handbook/product/personas/#amy-application-security-engineer)
* [Isaac (Infrastructure Engineer)](https://handbook.gitlab.com/handbook/product/personas/#isaac-infrastructure-security-engineer)
* [Alex (Security Operations Engineer)](https://handbook.gitlab.com/handbook/product/personas/#alex-security-operations-engineer)
* [Cameron (Compliance Manager)](https://handbook.gitlab.com/handbook/product/personas/#cameron-compliance-manager)
-->

View File

@ -18,7 +18,7 @@
- [ ] Ensure your UI suggestion align with the [Documentation Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide/)
- [ ] Engage ~"Technical Writing". They can help craft a term that best describes the feature(s) youre proposing.
- [ ] Follow the [product development workflow](https://about.gitlab.com/handbook/product-development-flow/#validation-phase-2-problem-validation) validation process to ensure you are solving a well understood problem and that the proposed change is understandable and non-disruptive to users. Navigation-specific research is mandatory for additions or when restructuring.
- [ ] Engage the [Foundations Product Manager](https://about.gitlab.com/handbook/product/categories/#foundations-group) for approval. The Foundations DRI (@cdybenko) will work with UX partners in product design, research, and technical writing, as applicable.
- [ ] Engage the [Foundations Product Manager](https://about.gitlab.com/handbook/product/categories/#foundations-group) for approval. The Foundations DRI (@jtucker_gl) will work with UX partners in product design, research, and technical writing, as applicable.
- [ ] Consider whether you need to [communicate the change somehow](https://design.gitlab.com/patterns/navigation#messaging-changes-to-users), or if you will have an interim period in the UI where your item will live in more than one place.
- [ ] Ensure engineers are familiar with the [implementation steps for navigation](https://docs.gitlab.com/ee/development/navigation_sidebar.html#navigation-sidebar).

View File

@ -23,7 +23,7 @@ A user story is a requirement for any functionality or feature and follows this
- _As a `<user role/customer>`, I want to `<JTBD>` so that I can `<achieve a benefit or result>`._
Please try to include one user story for the main [persona](https://about.gitlab.com/handbook/product/personas/#list-of-user-personas) who needs this feature.
Please try to include one user story for the main [persona](https://handbook.gitlab.com/handbook/product/personas/#list-of-user-personas) who needs this feature.
-->

View File

@ -8,7 +8,7 @@ Learn more about it in the handbook: https://about.gitlab.com/handbook/product-d
## Reach
<!-- Please describe who suffers from this problem. Consider referring to our personas, which are described at https://about.gitlab.com/handbook/product/personas/ -->
<!-- Please describe who suffers from this problem. Consider referring to our personas, which are described at https://handbook.gitlab.com/handbook/product/personas/ -->
<!-- Please also quantify the problem's reach using the following values, considering an aggregate across GitLab.com and self-managed:

View File

@ -2681,7 +2681,6 @@ Layout/LineLength:
- 'lib/gitlab/usage_data.rb'
- 'lib/gitlab/usage_data/topology.rb'
- 'lib/gitlab/usage_data_counters/base_counter.rb'
- 'lib/gitlab/usage_data_counters/editor_unique_counter.rb'
- 'lib/gitlab/usage_data_counters/gitlab_cli_activity_unique_counter.rb'
- 'lib/gitlab/usage_data_counters/hll_redis_counter.rb'
- 'lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb'

View File

@ -4052,7 +4052,6 @@ RSpec/FeatureCategory:
- 'spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/gitlab_cli_activity_unique_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb'
- 'spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb'

View File

@ -19,5 +19,4 @@ Style/KeywordParametersOrder:
- 'lib/gitlab/email/smime/signer.rb'
- 'lib/gitlab/exclusive_lease.rb'
- 'lib/gitlab/merge_requests/mergeability/results_store.rb'
- 'lib/gitlab/usage_data_counters/editor_unique_counter.rb'
- 'lib/microsoft_teams/notifier.rb'

View File

@ -171,14 +171,10 @@ export default {
return true;
}
return (
!this.sastReportsInInlineDiff ||
(!this.codequalityReportAvailable && !this.sastReportAvailable)
);
return !this.codequalityReportAvailable && !this.sastReportAvailable;
},
update(data) {
const { codequalityReportsComparer, sastReport } = data?.project?.mergeRequest || {};
this.activeProject = data?.project?.mergeRequest?.project;
if (
(sastReport?.status === FINDINGS_STATUS_PARSED || !this.sastReportAvailable) &&
@ -307,9 +303,6 @@ export default {
resourceId() {
return convertToGraphQLId('MergeRequest', this.getNoteableData.id);
},
sastReportsInInlineDiff() {
return this.glFeatures.sastReportsInInlineDiff;
},
},
watch: {
commit(newCommit, oldCommit) {
@ -347,10 +340,6 @@ export default {
isLoading: 'adjustView',
},
mounted() {
if (this.endpointCodequality) {
this.setCodequalityEndpoint(this.endpointCodequality);
}
if (this.shouldShow) {
this.fetchData();
}
@ -423,14 +412,12 @@ export default {
...mapActions(['startTaskList']),
...mapActions('diffs', [
'moveToNeighboringCommit',
'setCodequalityEndpoint',
'fetchDiffFilesMeta',
'fetchDiffFilesBatch',
'fetchFileByFile',
'loadCollapsedDiff',
'setFileForcedOpen',
'fetchCoverageFiles',
'fetchCodequality',
'rereadNoteHash',
'startRenderDiffsQueue',
'assignDiscussionsToDiff',
@ -576,10 +563,6 @@ export default {
this.fetchCoverageFiles();
}
if (this.endpointCodequality && !this.sastReportsInInlineDiff) {
this.fetchCodequality();
}
if (!this.isNotesFetched) {
notesEventHub.$emit('fetchNotesData');
}

View File

@ -1,32 +0,0 @@
<script>
import DiffInlineFindingsItem from './diff_inline_findings_item.vue';
export default {
components: { DiffInlineFindingsItem },
props: {
title: {
type: String,
required: true,
},
findings: {
type: Array,
required: true,
},
},
};
</script>
<template>
<div>
<h4 data-testid="diff-inline-findings-heading" class="gl-my-0 gl-font-base gl-font-regular">
{{ title }}
</h4>
<ul class="gl-list-style-none gl-mb-0 gl-p-0">
<diff-inline-findings-item
v-for="finding in findings"
:key="finding.description"
:finding="finding"
/>
</ul>
</div>
</template>

View File

@ -1,39 +0,0 @@
<script>
import { GlIcon } from '@gitlab/ui';
import { getSeverity } from '~/ci/reports/utils';
export default {
components: { GlIcon },
props: {
finding: {
type: Object,
required: true,
},
},
computed: {
enhancedFinding() {
return getSeverity(this.finding);
},
listText() {
return `${this.finding.severity} - ${this.finding.description}`;
},
},
};
</script>
<template>
<li class="gl-py-1 gl-font-regular gl-display-flex">
<span class="gl-mr-3">
<gl-icon
:size="12"
:name="enhancedFinding.name"
:class="enhancedFinding.class"
class="inline-findings-severity-icon"
/>
</span>
<span data-testid="description-plain-text" class="gl-display-flex">
{{ listText }}
</span>
</li>
</template>

View File

@ -1,32 +0,0 @@
<script>
import InlineFindings from './inline_findings.vue';
export default {
components: {
InlineFindings,
},
props: {
line: {
type: Object,
required: true,
},
},
computed: {
parsedCodeQuality() {
return (this.line.left ?? this.line.right)?.codequality;
},
codeQualityLineNumber() {
return this.parsedCodeQuality[0]?.line;
},
},
methods: {
hideInlineFindings() {
this.$emit('hideInlineFindings', this.codeQualityLineNumber);
},
},
};
</script>
<template>
<inline-findings :code-quality="parsedCodeQuality" @hideInlineFindings="hideInlineFindings" />
</template>

View File

@ -24,8 +24,8 @@ import * as utils from './diff_row_utils';
export default {
DiffGutterAvatars,
InlineFindingsFlagSwitcher: () =>
import('ee_component/diffs/components/inline_findings_flag_switcher.vue'),
InlineFindingsGutterIconDropdown: () =>
import('ee_component/diffs/components/inline_findings_gutter_icon_dropdown.vue'),
// Temporary mixin for migration from Vue.js 2 to @vue/compat
mixins: [compatFunctionalMixin],
@ -80,11 +80,6 @@ export default {
type: Function,
required: true,
},
inlineFindingsExpanded: {
type: Boolean,
required: false,
default: false,
},
},
classNameMap: memoize(
(props) => {
@ -336,9 +331,8 @@ export default {
:class="$options.parallelViewLeftLineType(props)"
>
<component
:is="$options.InlineFindingsFlagSwitcher"
:is="$options.InlineFindingsGutterIconDropdown"
v-if="$options.showCodequalityLeft(props) || $options.showSecurityLeft(props)"
:inline-findings-expanded="props.inlineFindingsExpanded"
:code-quality="props.line.left.codequality"
:sast="props.line.left.sast"
:file-path="props.filePath"
@ -478,7 +472,7 @@ export default {
:class="$options.classNameMapCellRight(props)"
>
<component
:is="$options.InlineFindingsFlagSwitcher"
:is="$options.InlineFindingsGutterIconDropdown"
v-if="$options.showCodequalityRight(props) || $options.showSecurityRight(props)"
:code-quality="props.line.right.codequality"
:sast="props.line.right.sast"

View File

@ -10,7 +10,6 @@ import { getCommentedLines } from '~/notes/components/multiline_comment_utils';
import { hide } from '~/tooltips';
import { pickDirection } from '../utils/diff_line';
import DiffCommentCell from './diff_comment_cell.vue';
import DiffLine from './diff_line.vue';
import DiffExpansionCell from './diff_expansion_cell.vue';
import DiffRow from './diff_row.vue';
import { isHighlighted } from './diff_row_utils';
@ -19,7 +18,6 @@ export default {
components: {
DiffExpansionCell,
DiffRow,
DiffLine,
DiffCommentCell,
DraftNote,
},
@ -48,11 +46,6 @@ export default {
default: false,
},
},
data() {
return {
inlineFindingsExpandedLines: [],
};
},
idState() {
return {
dragStart: null,
@ -87,9 +80,6 @@ export default {
this.sastDiff?.added?.length > 0
);
},
sastReportsInInlineDiff() {
return this.glFeatures.sastReportsInInlineDiff;
},
},
created() {
this.onDragOverThrottled = throttle((line) => this.onDragOver(line), 100, { leading: true });
@ -109,19 +99,6 @@ export default {
}
this.idState.dragStart = line;
},
hideInlineFindings(line) {
const index = this.inlineFindingsExpandedLines.indexOf(line);
if (index > -1) {
this.inlineFindingsExpandedLines.splice(index, 1);
}
},
toggleCodeQualityFindings(line) {
if (!this.inlineFindingsExpandedLines.includes(line)) {
this.inlineFindingsExpandedLines.push(line);
} else {
this.hideInlineFindings(line);
}
},
onDragOver(line) {
if (line.chunk !== this.idState.dragStart.chunk) return;
@ -266,12 +243,10 @@ export default {
:is-last-highlighted-line="isLastHighlightedLine(line) || index === commentedLines.endLine"
:inline="inline"
:index="index"
:inline-findings-expanded="inlineFindingsExpandedLines.includes(getCodeQualityLine(line))"
:file-line-coverage="fileLineCoverage"
:coverage-loaded="coverageLoaded"
@showCommentForm="(code) => singleLineComment(code, line)"
@setHighlightedRow="setHighlightedRow"
@toggleCodeQualityFindings="toggleCodeQualityFindings"
@toggleLineDiscussions="
({ lineCode, expanded }) =>
toggleLineDiscussions({ lineCode, fileHash: diffFile.file_hash, expanded })
@ -280,15 +255,6 @@ export default {
@startdragging="onStartDragging"
@stopdragging="onStopDragging"
/>
<!-- Don't display InlineFindings expanded section when sastReportsInInlineDiff is false -->
<diff-line
v-if="
inlineFindingsExpandedLines.includes(getCodeQualityLine(line)) && !sastReportsInInlineDiff
"
:key="line.line_code"
:line="line"
@hideInlineFindings="hideInlineFindings"
/>
<div
v-if="line.renderCommentRow"
:key="`dcr-${line.line_code || index}`"

View File

@ -1,41 +0,0 @@
<script>
import { GlButton } from '@gitlab/ui';
import { NEW_CODE_QUALITY_FINDINGS, NEW_SAST_FINDINGS } from '../i18n';
import DiffInlineFindings from './diff_inline_findings.vue';
export default {
i18n: {
newCodeQualityFindings: NEW_CODE_QUALITY_FINDINGS,
newSastFindings: NEW_SAST_FINDINGS,
},
components: { GlButton, DiffInlineFindings },
props: {
codeQuality: {
type: Array,
required: true,
},
},
};
</script>
<template>
<div
data-testid="inline-findings"
class="gl-relative inline-findings-list gl-border-top-1 gl-border-bottom-1 gl-bg-gray-10 gl-text-black-normal gl-pl-5 gl-pt-4 gl-pb-4"
>
<diff-inline-findings
v-if="codeQuality.length"
:title="$options.i18n.newCodeQualityFindings"
:findings="codeQuality"
/>
<gl-button
data-testid="inline-findings-close"
category="tertiary"
size="small"
icon="close"
class="gl-absolute gl-right-2 gl-top-2"
@click="$emit('hideInlineFindings')"
/>
</div>
</template>

View File

@ -34,7 +34,6 @@ export default function initDiffsApp(store = notesStore) {
projectPath: dataset.projectPath || '',
iid: dataset.iid || '',
endpointCoverage: dataset.endpointCoverage || '',
endpointCodequality: dataset.endpointCodequality || '',
codequalityReportAvailable: parseBoolean(dataset.codequalityReportAvailable),
sastReportAvailable: parseBoolean(dataset.sastReportAvailable),
helpPagePath: dataset.helpPagePath,

View File

@ -1,7 +1,7 @@
import * as actions from 'ee_else_ce/diffs/store/actions';
import * as getters from 'ee_else_ce/diffs/store/getters';
import createState from 'ee_else_ce/diffs/store/modules/diff_state';
import mutations from 'ee_else_ce/diffs/store/mutations';
import * as actions from '../actions';
export default () => ({
namespaced: true,

View File

@ -1,5 +1,5 @@
<script>
import { GlSorting, GlSortingItem } from '@gitlab/ui';
import { GlSorting } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapState } from 'vuex';
import { visitUrl } from '~/lib/utils/url_utility';
@ -9,7 +9,7 @@ import { SORT_DIRECTION_UI } from '~/search/sort/constants';
export default {
name: 'SortDropdown',
components: { GlSorting, GlSortingItem },
components: { GlSorting },
inject: ['namespace'],
computed: {
...mapState({
@ -29,6 +29,9 @@ export default {
activeOptionLabel() {
return this.activeOption?.label;
},
activeOptionKey() {
return this.activeOption?.key;
},
isAscending() {
return !this.sort.sortDesc;
},
@ -39,14 +42,8 @@ export default {
return FIELDS.filter(
(field) => this.tableSortableFields.includes(field.key) && field.sort,
).map((field) => ({
key: field.key,
label: field.label,
href: buildSortHref({
sortBy: field.key,
sortDesc: false,
filteredSearchBarTokens: this.filteredSearchBar.tokens,
filteredSearchBarSearchParam: this.filteredSearchBar.searchParam,
}),
text: field.label,
value: field.key,
}));
},
},
@ -64,6 +61,16 @@ export default {
}),
);
},
handleSortingItemClick(value) {
visitUrl(
buildSortHref({
sortBy: value,
sortDesc: false,
filteredSearchBarTokens: this.filteredSearchBar.tokens,
filteredSearchBarSearchParam: this.filteredSearchBar.searchParam,
}),
);
},
},
};
</script>
@ -76,15 +83,9 @@ export default {
:text="activeOptionLabel"
:is-ascending="isAscending"
:sort-direction-tool-tip="sortDirectionData.tooltip"
:sort-options="filteredOptions"
:sort-by="activeOptionKey"
@sortByChange="handleSortingItemClick"
@sortDirectionChange="handleSortDirectionChange"
>
<gl-sorting-item
v-for="option in filteredOptions"
:key="option.key"
:href="option.href"
:active="isActive(option.key)"
>
{{ option.label }}
</gl-sorting-item>
</gl-sorting>
/>
</template>

View File

@ -127,5 +127,6 @@ export default {
</gl-sprintf>
</span>
</p>
<slot></slot>
</div>
</template>

View File

@ -40,7 +40,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action only: [:show, :diffs] do
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
push_frontend_feature_flag(:sast_reports_in_inline_diff, project)
push_frontend_feature_flag(:mr_experience_survey, project)
push_frontend_feature_flag(:ci_job_failures_in_mr, project)
push_frontend_feature_flag(:mr_pipelines_graphql, project)

View File

@ -87,15 +87,15 @@ class ProjectsController < Projects::ApplicationController
@parent_group = Group.find_by(id: params[:namespace_id])
manageable_groups_count = current_user.manageable_groups(include_groups_with_developer_maintainer_access: true).count
manageable_groups = ::Groups::AcceptingProjectCreationsFinder.new(current_user).execute.limit(2)
if manageable_groups_count == 0 && !can?(current_user, :create_projects, current_user.namespace)
if manageable_groups.empty? && !can?(current_user, :create_projects, current_user.namespace)
return access_denied!
end
@current_user_group =
if manageable_groups_count == 1
current_user.manageable_groups(include_groups_with_developer_maintainer_access: true).first
if manageable_groups.one?
manageable_groups.first
end
@project = Project.new(namespace_id: @namespace&.id)

View File

@ -11,9 +11,9 @@ module Groups
[
current_user
.manageable_groups(include_groups_with_developer_maintainer_access: true)
.project_creation_allowed,
.project_creation_allowed(current_user),
owner_maintainer_groups_originating_from_group_shares
.project_creation_allowed,
.project_creation_allowed(current_user),
*developer_groups_originating_from_group_shares
]

View File

@ -15,7 +15,7 @@ module Groups
groups = ::Group.from_union(groups_accepting_project_transfers)
groups.project_creation_allowed
groups.project_creation_allowed(current_user)
end
private

View File

@ -53,7 +53,11 @@ module Mutations
# Only when the user is not an api user and the operation was successful
if !api_user? && service_response.success?
::Gitlab::UsageDataCounters::EditorUniqueCounter.track_snippet_editor_edit_action(author: current_user, project: project)
Gitlab::InternalEvents.track_event(
'g_edit_by_snippet_ide',
user: current_user,
project: project
)
end
snippet = service_response.payload[:snippet]

View File

@ -42,7 +42,11 @@ module Mutations
# Only when the user is not an api user and the operation was successful
if !api_user? && service_response.success?
::Gitlab::UsageDataCounters::EditorUniqueCounter.track_snippet_editor_edit_action(author: current_user, project: snippet.project)
Gitlab::InternalEvents.track_event(
'g_edit_by_snippet_ide',
user: current_user,
project: snippet.project
)
end
snippet = service_response.payload[:snippet]

View File

@ -211,7 +211,11 @@ class Group < Namespace
where(project_creation_level: project_creation_levels)
end
scope :project_creation_allowed, -> do
scope :excluding_restricted_visibility_levels_for_user, -> (user) do
user.can_admin_all_resources? ? all : where.not(visibility_level: Gitlab::CurrentSettings.restricted_visibility_levels)
end
scope :project_creation_allowed, -> (user) do
project_creation_allowed_on_levels = [
::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS,
::Gitlab::Access::MAINTAINER_PROJECT_ACCESS,
@ -227,7 +231,7 @@ class Group < Namespace
project_creation_allowed_on_levels.delete(nil)
end
with_project_creation_levels(project_creation_allowed_on_levels)
with_project_creation_levels(project_creation_allowed_on_levels).excluding_restricted_visibility_levels_for_user(user)
end
scope :shared_into_ancestors, -> (group) do

View File

@ -2,12 +2,21 @@
module Projects
class BranchRule
include GlobalID::Identification
extend Forwardable
attr_reader :project, :protected_branch
alias_method :branch_protection, :protected_branch
def_delegators(:protected_branch, :name, :group, :default_branch?, :created_at, :updated_at)
def_delegators(:protected_branch, :id, :name, :group, :default_branch?, :created_at, :updated_at)
def self.find(id)
protected_branch = ProtectedBranch.find(id)
new(protected_branch.project, protected_branch)
rescue ActiveRecord::RecordNotFound
raise ActiveRecord::RecordNotFound, "Couldn't find Projects::BranchRule with 'id'=#{id}"
end
def initialize(project, protected_branch)
@project = project

View File

@ -2,41 +2,16 @@
= content_for :head do
= stylesheet_link_tag 'mailers/highlighted_diff_email'
- if Feature.enabled?(:enhanced_review_email, @project, type: :gitlab_com_derisk)
%div{ style: "color:#333333;border-bottom:8px solid #ededed;font-weight:bold;line-height:1.4;padding: 20px 0;" }
- mr_link = link_to(@merge_request.to_reference(@project), project_merge_request_url(@project, @merge_request))
- mr_author_link = link_to(@author_name, user_url(@author))
= _('Merge request %{mr_link} was reviewed by %{mr_author}').html_safe % { mr_link: mr_link, mr_author: mr_author_link }
%div{ style: "color:#333333;border-bottom:8px solid #ededed;font-weight:bold;line-height:1.4;padding: 20px 0;" }
- mr_link = link_to(@merge_request.to_reference(@project), project_merge_request_url(@project, @merge_request))
- mr_author_link = link_to(@author_name, user_url(@author))
= _('Merge request %{mr_link} was reviewed by %{mr_author}').html_safe % { mr_link: mr_link, mr_author: mr_author_link }
- @notes.each do |note|
-# Get preloaded note discussion
- discussion = @discussions[note.discussion_id] if note.part_of_discussion?
-# Preload project for discussions first note
- discussion.first_note.project = @project if discussion&.first_note
- target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{note.id}")
= render 'note_email', note: note, diff_limit: 3, target_url: target_url, note_style: "border-bottom:4px solid #ededed; padding-bottom: 1em;", include_stylesheet_link: false, discussion: discussion, author: @author
= render_if_exists 'notify/review_summary'
- else
%table{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;margin:0 auto;border-collapse:separate;border-spacing:0;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;overflow:hidden;" }
%table{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
%tbody
%tr
%td{ style: "color:#333333;border-bottom:1px solid #ededed;font-weight:bold;line-height:1.4;padding: 20px 0;" }
- mr_link = link_to(@merge_request.to_reference(@project), project_merge_request_url(@project, @merge_request))
- mr_author_link = link_to(@author_name, user_url(@author))
= _('Merge request %{mr_link} was reviewed by %{mr_author}').html_safe % { mr_link: mr_link, mr_author: mr_author_link }
%tr
%td{ style: "overflow:hidden;line-height:1.4;display:grid;" }
- @notes.each do |note|
-# Get preloaded note discussion
- discussion = @discussions[note.discussion_id] if note.part_of_discussion?
-# Preload project for discussions first note
- discussion.first_note.project = @project if discussion&.first_note
- target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{note.id}")
= render 'note_email', note: note, diff_limit: 3, target_url: target_url, note_style: "border-bottom:1px solid #ededed; padding-bottom: 1em;", include_stylesheet_link: false, discussion: discussion, author: @author
= render_if_exists 'notify/review_summary'
- @notes.each do |note|
-# Get preloaded note discussion
- discussion = @discussions[note.discussion_id] if note.part_of_discussion?
-# Preload project for discussions first note
- discussion.first_note.project = @project if discussion&.first_note
- target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{note.id}")
= render 'note_email', note: note, diff_limit: 3, target_url: target_url, note_style: "border-bottom:4px solid #ededed; padding-bottom: 1em;", include_stylesheet_link: false, discussion: discussion, author: @author
= render_if_exists 'notify/review_summary'

View File

@ -83,13 +83,13 @@ module ClickHouse
paths = id_paths.map(&:second).map { |value| "'#{value}'" }.join(',')
query = ClickHouse::Client::Query.new(
raw_query: "DELETE FROM events WHERE path IN (#{paths})"
raw_query: "ALTER TABLE events DELETE WHERE path IN (#{paths})"
)
connection.execute(query)
query = ClickHouse::Client::Query.new(
raw_query: 'DELETE FROM event_namespace_paths WHERE namespace_id IN ({namespace_ids:Array(UInt64)})',
raw_query: 'ALTER TABLE event_namespace_paths DELETE WHERE namespace_id IN ({namespace_ids:Array(UInt64)})',
placeholders: { namespace_ids: id_paths.map(&:first).to_json }
)

View File

@ -1,9 +1,8 @@
---
name: enhanced_review_email
feature_issue_url:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141187
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/437582
milestone: '16.8'
group: group::code review
type: gitlab_com_derisk
name: redis_hll_property_name_tracking
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137890
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432866
milestone: '16.9'
type: wip
group: group::analytics instrumentation
default_enabled: false

View File

@ -29,6 +29,7 @@ Redis::Cluster.prepend(Gitlab::Patch::RedisCluster)
# this only instruments `RedisClient` used in `Sidekiq.redis`
RedisClient.register(Gitlab::Instrumentation::RedisClientMiddleware)
RedisClient.prepend(Gitlab::Patch::RedisClient)
if Gitlab::Redis::Workhorse.params[:cluster].present?
raise "Do not configure workhorse with a Redis Cluster as pub/sub commands are not cluster-compatible."

View File

@ -1,7 +1,16 @@
---
table_name: catalog_resource_components
feature_categories:
- pipeline_composition
description: CI component available in the CI Catalog
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127775
milestone: '16.3'
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_cell
allow_cross_joins:
- gitlab_main_clusterwide
allow_cross_transactions:
- gitlab_main_clusterwide
allow_cross_foreign_keys:
- gitlab_main_clusterwide
sharding_key:
project_id: projects

View File

@ -5,4 +5,12 @@ feature_categories:
description: Catalog resource versions that contain valid CI components.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124668
milestone: '16.2'
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_cell
allow_cross_joins:
- gitlab_main_clusterwide
allow_cross_transactions:
- gitlab_main_clusterwide
allow_cross_foreign_keys:
- gitlab_main_clusterwide
sharding_key:
project_id: projects

View File

@ -5,4 +5,12 @@ feature_categories:
description: Projects containing a catalog resource.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112482
milestone: '15.10'
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_cell
allow_cross_joins:
- gitlab_main_clusterwide
allow_cross_transactions:
- gitlab_main_clusterwide
allow_cross_foreign_keys:
- gitlab_main_clusterwide
sharding_key:
project_id: projects

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class RemoveIndexProtectedEnvironmentsOnProjectId < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '16.9'
INDEX_NAME = 'index_protected_environments_on_project_id'
def up
remove_concurrent_index_by_name :protected_environments, name: INDEX_NAME
end
def down
add_concurrent_index :protected_environments, :project_id, name: INDEX_NAME
end
end

View File

@ -0,0 +1 @@
1becea9a8277072f40d0fa7325338ae92e698b39e7c84b1be8ffe52832ef6794

View File

@ -35115,8 +35115,6 @@ CREATE INDEX index_protected_environments_on_approval_count_and_created_at ON pr
CREATE UNIQUE INDEX index_protected_environments_on_group_id_and_name ON protected_environments USING btree (group_id, name) WHERE (group_id IS NOT NULL);
CREATE INDEX index_protected_environments_on_project_id ON protected_environments USING btree (project_id);
CREATE UNIQUE INDEX index_protected_environments_on_project_id_and_name ON protected_environments USING btree (project_id, name);
CREATE INDEX index_protected_tag_create_access ON protected_tag_create_access_levels USING btree (protected_tag_id);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -26,7 +26,7 @@ You can use the [Web IDE](../../../user/project/web_ide/index.md) to commit chan
## Long-term vision
As a [new Software Developer to a team such as Sasha](https://about.gitlab.com/handbook/product/personas/#sasha-software-developer) with no local development environment, I should be able to:
As a [new Software Developer to a team such as Sasha](https://handbook.gitlab.com/handbook/product/personas/#sasha-software-developer) with no local development environment, I should be able to:
- Go to a repository on GitLab.com or self-managed.
- Click a button that will provide a list of current workspaces for this repository.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -1998,10 +1998,10 @@ to select a specific site profile and scanner profile.
### `dependencies`
Use the `dependencies` keyword to define a list of specific jobs to fetch [artifacts](#artifacts)
from. When `dependencies` is not defined in a job, all jobs in earlier stages are considered dependent
and the job fetches all artifacts from those jobs.
from. The specified jobs must all be in earlier stages. You can also set a job to download no artifacts at all.
You can also set a job to download no artifacts at all.
When `dependencies` is not defined in a job, all jobs in earlier stages are considered dependent
and the job fetches all artifacts from those jobs.
**Keyword type**: Job keyword. You can use it only as part of a job.
@ -2057,6 +2057,8 @@ the [stage](#stages) precedence.
- The job status does not matter. If a job fails or it's a manual job that isn't triggered, no error occurs.
- If the artifacts of a dependent job are [expired](#artifactsexpire_in) or
[deleted](../jobs/job_artifacts.md#delete-job-log-and-artifacts), then the job fails.
- To fetch artifacts from a job in the same stage, you must use [`needs:artifacts`](#needsartifacts).
You should not combine `dependencies` with `needs` in the same job.
### `environment`
@ -2917,8 +2919,7 @@ In this example:
**Additional details**:
- In GitLab 12.6 and later, you can't combine the [`dependencies`](#dependencies) keyword
with `needs`.
- You should not combine `needs` with [`dependencies`](#dependencies) in the same job.
#### `needs:project` **(PREMIUM ALL)**

View File

@ -199,22 +199,16 @@ In this example, I found some UI text I'd like to change.
In the upper-right corner in GitLab, I selected my avatar and then **Preferences**.
I want to change this text:
![UI text](img/ui_text_before.png)
![UI text](img/ui_color_theme_before.png)
Other settings on the page start with the word `Customize` and skip the `This setting allows you to` part.
I'll update this phrase to match the others.
Let's clarify `Customize the color of GitLab.` by changing the phrase to `Customize the color theme of the GitLab UI.`.
NOTE:
As this text has already been [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116472) when developing this tutorial, you can instead search for `Customize the appearance of the syntax` to find the files that were changed.
1. Search the `gitlab-development-kit/gitlab` directory for the string `Customize the color`.
1. Search the `gitlab-development-kit/gitlab` directory for the string `This setting allows you to customize`.
The results shows 1 `.haml` file, and several `.po` files.
The results show one `.haml` file, two `.md` files, one `.pot` file, and
several `.po` files.
1. Open the `.haml` file. This file is where the UI text resides.
1. Update the string. In this case, I'll remove the words before `customize`
and start the word `customize` with a capital `C`.
1. Open the `app/views/profiles/preferences/show.html.haml` file. This file is where the UI text resides.
1. Update the string from `Customize the color of GitLab.` to `Customize the color theme of the GitLab UI.`.
1. Save the file.
You can check that you were successful:
@ -229,7 +223,7 @@ You can check that you were successful:
- Refresh the web browser where you're viewing the GDK.
The changes should be displayed. Take a screenshot.
![UI text](img/ui_text_after.png)
![UI text](img/ui_color_theme_after.png)
### Update the translation files

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -90,7 +90,7 @@ This chart contains several types of items:
AB["batch_comments~~draft_note.vue"]
AC["diffs~~diff_comment_cell.vue"]
AD["diffs~~diff_gutter_avatars.vue"]
AE["ee-diffs~~inline_findings_flag_switcher.vue"]
AE["ee-diffs~~inline_findings_gutter_icon_dropdown.vue"]
AF["notes~~noteable_note.vue"]
AG["notes~~note_actions.vue"]
AH["notes~~note_body.vue"]

View File

@ -26,7 +26,7 @@ GitLab is creating AI-assisted features across our DevSecOps platform. These fea
| Helps you remediate vulnerabilities more efficiently, boost your skills, and write more secure code. <br><br><i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=6sDf73QOav8) | [Vulnerability summary](application_security/vulnerabilities/index.md#explaining-a-vulnerability) | **(ULTIMATE SAAS BETA)** |
| Generates a merge request containing the changes required to mitigate a vulnerability. | [Vulnerability resolution](application_security/vulnerabilities/index.md#explaining-a-vulnerability) | **(ULTIMATE SAAS EXPERIMENT)** |
| Helps you understand code by explaining it in English language. <br><br><i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=1izKaLmmaCA) | [Code explanation](#explain-code-in-the-web-ui-with-code-explanation) | **(ULTIMATE SAAS EXPERIMENT)** |
| Processes and generates text and code in a conversational manner. Helps you quickly identify useful information in large volumes of text in issues, epics, code, and GitLab documentation. | [GitLab Duo Chat](gitlab_duo_chat.md) | **(ULTIMATE BETA)** |
| Processes and generates text and code in a conversational manner. Helps you quickly identify useful information in large volumes of text in issues, epics, code, and GitLab documentation. | [GitLab Duo Chat](gitlab_duo_chat.md) | **(ULTIMATE ALL BETA)** |
| Assists you in determining the root cause for a pipeline failure and failed CI/CD build. | [Root cause analysis](#root-cause-analysis) | **(ULTIMATE SAAS EXPERIMENT)** |
| Assists you with predicting productivity metrics and identifying anomalies across your software development lifecycle. | [Value stream forecasting](#forecast-deployment-frequency-with-value-stream-forecasting) | **(ULTIMATE ALL EXPERIMENT)** |
| Processes and responds to your questions about your application's usage data. | [Product Analytics](product_analytics/index.md) | **(ULTIMATE SAAS EXPERIMENT)** |

View File

@ -1,7 +1,7 @@
---
stage: Deploy
group: Environments
info: A tutorial for deploying a GitLab repository using Flux
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Tutorial: Deploy a Git repository using Flux **(FREE ALL)**

View File

@ -1,7 +1,7 @@
---
stage: Deploy
group: Environments
info: A tutorial for deploying an OCI artifact using Flux
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Tutorial: Deploy an OCI artifact using Flux **(FREE ALL)**

View File

@ -1,7 +1,7 @@
---
stage: Deploy
group: Environments
info: A tutorial using Flux
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Tutorial: Set up Flux for GitOps **(FREE ALL)**

View File

@ -63,9 +63,9 @@ GitLab in a Kubernetes cluster, you might need a different version of Kubernetes
You can upgrade your
Kubernetes version to a supported version at any time:
- 1.29 (support ends when GitLab version 17.10 is released or when 1.32 becomes supported)
- 1.28 (support ends when GitLab version 17.5 is released or when 1.31 becomes supported)
- 1.27 (support ends when GitLab version 17.2 is released or when 1.30 becomes supported)
- 1.26 (support ends when GitLab version 16.10 is released or when 1.29 becomes supported)
GitLab aims to support a new minor Kubernetes version three months after its initial release. GitLab supports at least three production-ready Kubernetes minor
versions at any given time.

View File

@ -83,7 +83,7 @@ To change the default collapse behavior for a file type:
docs/** gitlab-generated
# Do not collapse package-lock.json
package-json -gitlab-generated
package-lock.json -gitlab-generated
```
1. Commit, push, and merge your changes into your default branch.

View File

@ -32,7 +32,8 @@ module API
return unless find_user_from_warden
Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count
Gitlab::UsageDataCounters::EditorUniqueCounter.track_web_ide_edit_action(author: current_user, project: user_project)
Gitlab::InternalEvents.track_event('g_edit_by_web_ide', user: current_user, project: user_project)
namespace = user_project.namespace
Gitlab::Tracking.event(

View File

@ -16,13 +16,17 @@ module Gitlab
yield
rescue ::Redis::BaseError, ::RedisClient::Error => ex
if ex.message.start_with?('MOVED', 'ASK')
instrumentation_class.instance_count_cluster_redirection(ex)
else
instrumentation_class.instance_count_exception(ex)
Thread.current[:redis_client_error_count] ||= 0
# skip instrumentation if the error is a connection error happening for the first time as instrumentation
# middlewares are called within `ensure_connected` blocks. Connection retries are not known to the middleware.
# Refer to https://github.com/redis-rb/redis-client/issues/119#issuecomment-1829703792
unless ex.is_a?(::RedisClient::ConnectionError) && Thread.current[:redis_client_error_count] == 0
instrument_errors(instrumentation_class, ex)
end
instrumentation_class.log_exception(ex)
Thread.current[:redis_client_error_count] += 1 if ex.is_a?(::RedisClient::Error)
raise ex
ensure
duration = Gitlab::Metrics::System.monotonic_time - start
@ -80,6 +84,18 @@ module Gitlab
def exclude_from_apdex?(commands)
commands.any? { |command| APDEX_EXCLUDE.include?(command.first.to_s.downcase) }
end
private
def instrument_errors(instrumentation_class, error)
if error.message.start_with?('MOVED', 'ASK')
instrumentation_class.instance_count_cluster_redirection(error)
else
instrumentation_class.instance_count_exception(error)
end
instrumentation_class.log_exception(error)
end
end
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Gitlab
module Patch
module RedisClient
# This patch resets the connection error tracker after each call to prevent state leak
# across calls and requests.
#
# The purpose of the tracker is to silence RedisClient::ConnectionErrors during reconnection attempts.
# More details found in https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/2564#note_1665334335
def ensure_connected(retryable: true)
super
ensure
Thread.current[:redis_client_error_count] = 0
end
end
end
end

View File

@ -1,55 +0,0 @@
# frozen_string_literal: true
module Gitlab
module UsageDataCounters
module EditorUniqueCounter
EDIT_BY_SNIPPET_EDITOR = 'g_edit_by_snippet_ide'
EDIT_BY_SFE = 'g_edit_by_sfe'
EDIT_BY_WEB_IDE = 'g_edit_by_web_ide'
EDIT_CATEGORY = 'ide_edit'
class << self
def track_web_ide_edit_action(author:, project:)
track_internal_event(EDIT_BY_WEB_IDE, author, project)
end
def count_web_ide_edit_actions(date_from:, date_to:)
count_unique(EDIT_BY_WEB_IDE, date_from, date_to)
end
def track_sfe_edit_action(author:, project:)
track_internal_event(EDIT_BY_SFE, author, project)
end
def count_sfe_edit_actions(date_from:, date_to:)
count_unique(EDIT_BY_SFE, date_from, date_to)
end
def track_snippet_editor_edit_action(author:, project:)
track_internal_event(EDIT_BY_SNIPPET_EDITOR, author, project)
end
def count_snippet_editor_edit_actions(date_from:, date_to:)
count_unique(EDIT_BY_SNIPPET_EDITOR, date_from, date_to)
end
private
def track_internal_event(event_name, author, project = nil)
return unless author
Gitlab::InternalEvents.track_event(
event_name,
user: author,
project: project,
namespace: project&.namespace
)
end
def count_unique(actions, date_from, date_to)
Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: actions, start_date: date_from, end_date: date_to)
end
end
end
end
end

View File

@ -29,18 +29,20 @@ module Gitlab
#
# event_name - The event name.
# values - One or multiple values counted.
# property_name - Name of the values counted.
# time - Time of the action, set to Time.current.
def track_event(event_name, values:, time: Time.current)
track(values, event_name, time: time)
def track_event(event_name, values:, property_name: nil, time: Time.current)
track(values, event_name, property_name: property_name, time: time)
end
# Count unique events for a given time range.
#
# event_names - The list of the events to count.
# property_names - The list of the values for which the events are to be counted.
# start_date - The start date of the time range.
# end_date - The end date of the time range.
def unique_events(event_names:, start_date:, end_date:)
count_unique_events(event_names: event_names, start_date: start_date, end_date: end_date)
def unique_events(event_names:, start_date:, end_date:, property_name: nil)
count_unique_events(event_names: event_names, property_name: property_name, start_date: start_date, end_date: end_date)
end
def known_event?(event_name)
@ -52,19 +54,19 @@ module Gitlab
end
def calculate_events_union(event_names:, start_date:, end_date:)
count_unique_events(event_names: event_names, start_date: start_date, end_date: end_date)
count_unique_events(event_names: event_names, property_name: nil, start_date: start_date, end_date: end_date)
end
private
def track(values, event_name, time: Time.zone.now)
def track(values, event_name, property_name:, time: Time.zone.now)
event = event_for(event_name)
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(UnknownEvent.new("Unknown event #{event_name}")) unless event.present?
return if event.blank?
return unless Feature.enabled?(:redis_hll_tracking, type: :ops)
Gitlab::Redis::HLL.add(key: redis_key(event, time), value: values, expiry: KEY_EXPIRY_LENGTH)
Gitlab::Redis::HLL.add(key: redis_key(event_with_property_name(event, property_name), time), value: values, expiry: KEY_EXPIRY_LENGTH)
rescue StandardError => e
# Ignore any exceptions unless is dev or test env
@ -72,8 +74,8 @@ module Gitlab
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
end
def count_unique_events(event_names:, start_date:, end_date:)
events = events_for(Array(event_names).map(&:to_s))
def count_unique_events(event_names:, start_date:, end_date:, property_name:)
events = events_with_property_names(event_names, property_name)
keys = keys_for_aggregation(events: events, start_date: start_date, end_date: end_date)
@ -82,6 +84,19 @@ module Gitlab
redis_usage_data { Gitlab::Redis::HLL.count(keys: keys) }
end
def events_with_property_names(event_names, property_name)
event_names = Array(event_names).map(&:to_s)
known_events.filter_map do |event|
next unless event_names.include?(event[:name])
property_name ? event_with_property_name(event, property_name) : event
end
end
def event_with_property_name(event, property_name)
event.merge(property_name: property_name)
end
def load_events
events = Gitlab::Usage::MetricDefinition.all.map do |d|
next unless d.available?
@ -102,21 +117,30 @@ module Gitlab
known_events.find { |event| event[:name] == event_name.to_s }
end
def events_for(event_names)
known_events.select { |event| event_names.include?(event[:name]) }
end
def redis_key(event, time)
key = redis_key_base(event[:name])
key = redis_key_base(event)
year_week = time.strftime('%G-%V')
"{#{REDIS_SLOT}}_#{key}-#{year_week}"
end
def redis_key_base(event_name)
def redis_key_base(event)
event_name = event[:name]
raise UnknownEvent, "Unknown event #{event_name}" unless known_events_names.include?(event_name.to_s)
key_overrides.fetch(event_name, event_name)
property_name = event[:property_name]
key = event_name
if Feature.enabled?(:redis_hll_property_name_tracking, type: :wip) && property_name
key = "#{key}-#{formatted_property_name(property_name)}"
end
key_overrides.fetch(key, key)
end
def formatted_property_name(property_name)
# simplify to format from EventDefinitions.unique_properties
property_name.to_s.split('.').first.to_sym
end
def key_overrides

View File

@ -1 +1,127 @@
{}
# This file lists all of the internal events that need to be saved with their legacy HLL Redis keys
#
# This file has been generated using the script included in
# the description of https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137890
#
# It is only safe to regenerate it using the same script if the
# :redis_hll_property_name_tracking feature flag is disabled on prod environment.
---
agent_users_using_ci_tunnel-user: agent_users_using_ci_tunnel
ci_template_included-project: ci_template_included
exclude_anonymised_users-user: exclude_anonymised_users
g_compliance_dashboard-user: g_compliance_dashboard
g_edit_by_sfe-user: g_edit_by_sfe
g_edit_by_snippet_ide-user: g_edit_by_snippet_ide
g_edit_by_web_ide-user: g_edit_by_web_ide
g_project_management_epic_blocked_added-user: g_project_management_epic_blocked_added
g_project_management_epic_blocked_removed-user: g_project_management_epic_blocked_removed
g_project_management_epic_blocking_added-user: g_project_management_epic_blocking_added
g_project_management_epic_blocking_removed-user: g_project_management_epic_blocking_removed
g_project_management_epic_closed-user: g_project_management_epic_closed
g_project_management_epic_created-user: g_project_management_epic_created
g_project_management_epic_cross_referenced-user: g_project_management_epic_cross_referenced
g_project_management_epic_destroyed-user: g_project_management_epic_destroyed
g_project_management_epic_issue_added-user: g_project_management_epic_issue_added
g_project_management_epic_issue_moved_from_project-user: g_project_management_epic_issue_moved_from_project
g_project_management_epic_issue_removed-user: g_project_management_epic_issue_removed
g_project_management_epic_related_added-user: g_project_management_epic_related_added
g_project_management_epic_related_removed-user: g_project_management_epic_related_removed
g_project_management_epic_reopened-user: g_project_management_epic_reopened
g_project_management_epic_users_changing_labels-user: g_project_management_epic_users_changing_labels
g_project_management_issue_added_to_epic-user: g_project_management_issue_added_to_epic
g_project_management_issue_assignee_changed-user: g_project_management_issue_assignee_changed
g_project_management_issue_changed_epic-user: g_project_management_issue_changed_epic
g_project_management_issue_cloned-user: g_project_management_issue_cloned
g_project_management_issue_closed-user: g_project_management_issue_closed
g_project_management_issue_comment_added-user: g_project_management_issue_comment_added
g_project_management_issue_comment_edited-user: g_project_management_issue_comment_edited
g_project_management_issue_comment_removed-user: g_project_management_issue_comment_removed
g_project_management_issue_created-user: g_project_management_issue_created
g_project_management_issue_cross_referenced-user: g_project_management_issue_cross_referenced
g_project_management_issue_description_changed-user: g_project_management_issue_description_changed
g_project_management_issue_design_comments_removed-user: g_project_management_issue_design_comments_removed
g_project_management_issue_designs_added-user: g_project_management_issue_designs_added
g_project_management_issue_designs_modified-user: g_project_management_issue_designs_modified
g_project_management_issue_designs_removed-user: g_project_management_issue_designs_removed
g_project_management_issue_due_date_changed-user: g_project_management_issue_due_date_changed
g_project_management_issue_health_status_changed-user: g_project_management_issue_health_status_changed
g_project_management_issue_iteration_changed-user: g_project_management_issue_iteration_changed
g_project_management_issue_label_changed-user: g_project_management_issue_label_changed
g_project_management_issue_locked-user: g_project_management_issue_locked
g_project_management_issue_made_confidential-user: g_project_management_issue_made_confidential
g_project_management_issue_made_visible-user: g_project_management_issue_made_visible
g_project_management_issue_marked_as_duplicate-user: g_project_management_issue_marked_as_duplicate
g_project_management_issue_milestone_changed-user: g_project_management_issue_milestone_changed
g_project_management_issue_moved-user: g_project_management_issue_moved
g_project_management_issue_promoted_to_epic-user: g_project_management_issue_promoted_to_epic
g_project_management_issue_related-user: g_project_management_issue_related
g_project_management_issue_removed_from_epic-user: g_project_management_issue_removed_from_epic
g_project_management_issue_reopened-user: g_project_management_issue_reopened
g_project_management_issue_time_estimate_changed-user: g_project_management_issue_time_estimate_changed
g_project_management_issue_time_spent_changed-user: g_project_management_issue_time_spent_changed
g_project_management_issue_title_changed-user: g_project_management_issue_title_changed
g_project_management_issue_unlocked-user: g_project_management_issue_unlocked
g_project_management_issue_unrelated-user: g_project_management_issue_unrelated
g_project_management_issue_weight_changed-user: g_project_management_issue_weight_changed
g_project_management_users_awarding_epic_emoji-user: g_project_management_users_awarding_epic_emoji
g_project_management_users_creating_epic_notes-user: g_project_management_users_creating_epic_notes
g_project_management_users_destroying_epic_notes-user: g_project_management_users_destroying_epic_notes
g_project_management_users_epic_issue_added_from_epic-user: g_project_management_users_epic_issue_added_from_epic
g_project_management_users_removing_epic_emoji-user: g_project_management_users_removing_epic_emoji
g_project_management_users_setting_epic_confidential-user: g_project_management_users_setting_epic_confidential
g_project_management_users_setting_epic_due_date_as_fixed-user: g_project_management_users_setting_epic_due_date_as_fixed
g_project_management_users_setting_epic_due_date_as_inherited-user: g_project_management_users_setting_epic_due_date_as_inherited
g_project_management_users_setting_epic_start_date_as_fixed-user: g_project_management_users_setting_epic_start_date_as_fixed
g_project_management_users_setting_epic_start_date_as_inherited-user: g_project_management_users_setting_epic_start_date_as_inherited
g_project_management_users_setting_epic_visible-user: g_project_management_users_setting_epic_visible
g_project_management_users_updating_epic_descriptions-user: g_project_management_users_updating_epic_descriptions
g_project_management_users_updating_epic_notes-user: g_project_management_users_updating_epic_notes
g_project_management_users_updating_epic_parent-user: g_project_management_users_updating_epic_parent
g_project_management_users_updating_epic_titles-user: g_project_management_users_updating_epic_titles
g_project_management_users_updating_fixed_epic_due_date-user: g_project_management_users_updating_fixed_epic_due_date
g_project_management_users_updating_fixed_epic_start_date-user: g_project_management_users_updating_fixed_epic_start_date
geo_secondary_git_op_action-user: geo_secondary_git_op_action
i_analytics_dev_ops_adoption-user: i_analytics_dev_ops_adoption
i_analytics_dev_ops_score-user: i_analytics_dev_ops_score
i_code_review_saved_replies_create-user: i_code_review_saved_replies_create
i_code_review_saved_replies_use-user: i_code_review_saved_replies_use
i_code_review_saved_replies_use_in_mr-user: i_code_review_saved_replies_use_in_mr
i_code_review_saved_replies_use_in_other-user: i_code_review_saved_replies_use_in_other
i_code_review_user_create_mr-user: i_code_review_user_create_mr
i_quickactions_remove_email_multiple-user: i_quickactions_remove_email_multiple
i_quickactions_remove_email_single-user: i_quickactions_remove_email_single
insights_chart_item_clicked-user: insights_chart_item_clicked
insights_issue_chart_item_clicked-user: insights_issue_chart_item_clicked
k8s_api_proxy_requests_unique_users_via_ci_access-user: k8s_api_proxy_requests_unique_users_via_ci_access
k8s_api_proxy_requests_unique_users_via_pat_access-user: k8s_api_proxy_requests_unique_users_via_pat_access
k8s_api_proxy_requests_unique_users_via_user_access-user: k8s_api_proxy_requests_unique_users_via_user_access
p_analytics_ci_cd_deployment_frequency-user: p_analytics_ci_cd_deployment_frequency
p_analytics_ci_cd_lead_time-user: p_analytics_ci_cd_lead_time
p_analytics_ci_cd_pipelines-user: p_analytics_ci_cd_pipelines
project_management_users_checking_epic_task-user: project_management_users_checking_epic_task
project_management_users_unchecking_epic_task-user: project_management_users_unchecking_epic_task
unique_users_visiting_ci_catalog-user: unique_users_visiting_ci_catalog
user_created_custom_dashboard-user: user_created_custom_dashboard
user_created_custom_visualization-user: user_created_custom_visualization
user_edited_cluster_configuration-user: user_edited_cluster_configuration
user_edited_custom_dashboard-user: user_edited_custom_dashboard
user_viewed_cluster_configuration-user: user_viewed_cluster_configuration
user_viewed_custom_dashboard-user: user_viewed_custom_dashboard
user_viewed_dashboard_designer-user: user_viewed_dashboard_designer
user_viewed_dashboard_list-user: user_viewed_dashboard_list
user_viewed_instrumentation_directions-user: user_viewed_instrumentation_directions
user_viewed_visualization_designer-user: user_viewed_visualization_designer
user_visited_dashboard-user: user_visited_dashboard
value_streams_dashboard_change_failure_rate_link_clicked-user: value_streams_dashboard_change_failure_rate_link_clicked
value_streams_dashboard_cycle_time_link_clicked-user: value_streams_dashboard_cycle_time_link_clicked
value_streams_dashboard_deployment_frequency_link_clicked-user: value_streams_dashboard_deployment_frequency_link_clicked
value_streams_dashboard_deploys_link_clicked-user: value_streams_dashboard_deploys_link_clicked
value_streams_dashboard_issues_completed_link_clicked-user: value_streams_dashboard_issues_completed_link_clicked
value_streams_dashboard_issues_link_clicked-user: value_streams_dashboard_issues_link_clicked
value_streams_dashboard_lead_time_for_changes_link_clicked-user: value_streams_dashboard_lead_time_for_changes_link_clicked
value_streams_dashboard_lead_time_link_clicked-user: value_streams_dashboard_lead_time_link_clicked
value_streams_dashboard_merge_request_throughput_link_clicked-user: value_streams_dashboard_merge_request_throughput_link_clicked
value_streams_dashboard_metric_link_clicked-user: value_streams_dashboard_metric_link_clicked
value_streams_dashboard_time_to_restore_service_link_clicked-user: value_streams_dashboard_time_to_restore_service_link_clicked
value_streams_dashboard_vulnerability_critical_link_clicked-user: value_streams_dashboard_vulnerability_critical_link_clicked
value_streams_dashboard_vulnerability_high_link_clicked-user: value_streams_dashboard_vulnerability_high_link_clicked

View File

@ -1585,11 +1585,6 @@ msgstr ""
msgid "0 B"
msgstr ""
msgid "1 Code Quality finding"
msgid_plural "%d Code Quality findings"
msgstr[0] ""
msgstr[1] ""
msgid "1 Day"
msgid_plural "%d Days"
msgstr[0] ""
@ -5517,9 +5512,6 @@ msgstr ""
msgid "An unexpected error occurred while communicating with the Web Terminal."
msgstr ""
msgid "An unexpected error occurred while loading the code quality diff."
msgstr ""
msgid "An unexpected error occurred while starting the Web Terminal."
msgstr ""
@ -23063,6 +23055,18 @@ msgstr ""
msgid "Google Play service account key."
msgstr ""
msgid "GoogleArtifactRegistry|An error occurred while fetching the artifacts."
msgstr ""
msgid "GoogleArtifactRegistry|Open in Google Cloud"
msgstr ""
msgid "GoogleArtifactRegistry|Project ID"
msgstr ""
msgid "GoogleArtifactRegistry|Repository name"
msgstr ""
msgid "GoogleCloud|Cancel"
msgstr ""

View File

@ -18,7 +18,7 @@ RSpec.describe 'Groups > Members > Sort members', :js, feature_category: :groups
def expect_sort_by(text, sort_direction)
within_testid('members-sort-dropdown') do
expect(page).to have_css('button[aria-haspopup="menu"]', text: text)
expect(page).to have_css('button[aria-haspopup="listbox"]', text: text)
expect(page).to have_button("Sort direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}")
end
end

View File

@ -148,7 +148,7 @@ RSpec.describe 'Projects > Members > Sorting', :js, feature_category: :groups_an
def expect_sort_by(text, sort_direction)
within('[data-testid="members-sort-dropdown"]') do
expect(page).to have_css('button[aria-haspopup="menu"]', text: text)
expect(page).to have_css('button[aria-haspopup="listbox"]', text: text)
expect(page).to have_button("Sort direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}")
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Groups::AcceptingProjectTransfersFinder do
RSpec.describe Groups::AcceptingProjectTransfersFinder, feature_category: :groups_and_projects do
let_it_be(:user) { create(:user) }
let_it_be(:group_where_direct_owner) { create(:group) }
let_it_be(:subgroup_of_group_where_direct_owner) { create(:group, parent: group_where_direct_owner) }

View File

@ -195,10 +195,6 @@ describe('diffs/components/app', () => {
describe('codequality diff', () => {
it('does not fetch code quality data on FOSS', () => {
createComponent();
jest.spyOn(wrapper.vm, 'fetchCodequality');
wrapper.vm.fetchData(false);
expect(wrapper.vm.fetchCodequality).not.toHaveBeenCalled();
expect(codeQualityAndSastQueryHandlerSuccess).not.toHaveBeenCalled();
});
});

View File

@ -1,38 +0,0 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DiffInlineFindingsItem from '~/diffs/components/diff_inline_findings_item.vue';
import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants';
import { multipleFindingsArrCodeQualityScale } from '../mock_data/inline_findings';
let wrapper;
const [codeQualityFinding] = multipleFindingsArrCodeQualityScale;
const findIcon = () => wrapper.findComponent(GlIcon);
const findDescriptionPlainText = () => wrapper.findByTestId('description-plain-text');
describe('DiffCodeQuality', () => {
const createWrapper = () => {
return shallowMountExtended(DiffInlineFindingsItem, {
propsData: {
finding: codeQualityFinding,
},
});
};
it('shows icon for given degradation', () => {
wrapper = createWrapper();
expect(findIcon().exists()).toBe(true);
expect(findIcon().attributes()).toMatchObject({
class: `inline-findings-severity-icon ${SEVERITY_CLASSES[codeQualityFinding.severity]}`,
name: SEVERITY_ICONS[codeQualityFinding.severity],
size: '12',
});
});
it('should render severity + description in plain text', () => {
wrapper = createWrapper();
expect(findDescriptionPlainText().text()).toContain(codeQualityFinding.severity);
expect(findDescriptionPlainText().text()).toContain(codeQualityFinding.description);
});
});

View File

@ -1,33 +0,0 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DiffInlineFindings from '~/diffs/components/diff_inline_findings.vue';
import DiffInlineFindingsItem from '~/diffs/components/diff_inline_findings_item.vue';
import { NEW_CODE_QUALITY_FINDINGS } from '~/diffs/i18n';
import { multipleFindingsArrCodeQualityScale } from '../mock_data/inline_findings';
let wrapper;
const heading = () => wrapper.findByTestId('diff-inline-findings-heading');
const diffInlineFindingsItems = () => wrapper.findAllComponents(DiffInlineFindingsItem);
describe('DiffInlineFindings', () => {
const createWrapper = () => {
return shallowMountExtended(DiffInlineFindings, {
propsData: {
title: NEW_CODE_QUALITY_FINDINGS,
findings: multipleFindingsArrCodeQualityScale,
},
});
};
it('renders the title correctly', () => {
wrapper = createWrapper();
expect(heading().text()).toBe(NEW_CODE_QUALITY_FINDINGS);
});
it('renders the correct number of DiffInlineFindingsItem components with correct props', () => {
wrapper = createWrapper();
expect(diffInlineFindingsItems()).toHaveLength(multipleFindingsArrCodeQualityScale.length);
expect(diffInlineFindingsItems().wrappers[0].props('finding')).toEqual(
wrapper.props('findings')[0],
);
});
});

View File

@ -1,65 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import DiffLine from '~/diffs/components/diff_line.vue';
import InlineFindings from '~/diffs/components/inline_findings.vue';
const EXAMPLE_LINE_NUMBER = 3;
const EXAMPLE_DESCRIPTION = 'example description';
const EXAMPLE_SEVERITY = 'example severity';
const left = {
line: {
left: {
codequality: [
{
line: EXAMPLE_LINE_NUMBER,
description: EXAMPLE_DESCRIPTION,
severity: EXAMPLE_SEVERITY,
},
],
},
},
};
const right = {
line: {
right: {
codequality: [
{
line: EXAMPLE_LINE_NUMBER,
description: EXAMPLE_DESCRIPTION,
severity: EXAMPLE_SEVERITY,
},
],
},
},
};
const mockData = [right, left];
describe('DiffLine', () => {
const createWrapper = (propsData) => {
return shallowMount(DiffLine, { propsData });
};
it('should emit event when hideInlineFindings is called', () => {
const wrapper = createWrapper(right);
wrapper.findComponent(InlineFindings).vm.$emit('hideInlineFindings');
expect(wrapper.emitted()).toEqual({
hideInlineFindings: [[EXAMPLE_LINE_NUMBER]],
});
});
mockData.forEach((element) => {
it('should set correct props for InlineFindings', () => {
const wrapper = createWrapper(element);
expect(wrapper.findComponent(InlineFindings).props('codeQuality')).toEqual([
{
line: EXAMPLE_LINE_NUMBER,
description: EXAMPLE_DESCRIPTION,
severity: EXAMPLE_SEVERITY,
},
]);
});
});
});

View File

@ -1,11 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import { throttle } from 'lodash';
import DiffView from '~/diffs/components/diff_view.vue';
import DiffLine from '~/diffs/components/diff_line.vue';
import { diffCodeQuality } from '../mock_data/inline_findings';
jest.mock('lodash/throttle', () => jest.fn((fn) => fn));
const lodash = jest.requireActual('lodash');
@ -19,7 +17,7 @@ describe('DiffView', () => {
const setSelectedCommentPosition = jest.fn();
const getDiffRow = (wrapper) => wrapper.findComponent(DiffRow).vm;
const createWrapper = ({ props, flag = false } = {}) => {
const createWrapper = ({ props } = {}) => {
Vue.use(Vuex);
const batchComments = {
@ -51,21 +49,10 @@ describe('DiffView', () => {
diffFile: { file_hash: '123' },
diffLines: [],
...props,
provide: {
glFeatures: {
sastReportsInInlineDiff: flag,
},
},
};
const provide = {
glFeatures: {
sastReportsInInlineDiff: flag,
},
};
const stubs = { DiffExpansionCell, DiffRow, DiffCommentCell, DraftNote };
return shallowMount(DiffView, { propsData, provide, store, stubs });
return shallowMount(DiffView, { propsData, store, stubs });
};
beforeEach(() => {
@ -76,32 +63,6 @@ describe('DiffView', () => {
throttle.mockReset();
});
it('does not render a diff-line component when there is no finding', () => {
const wrapper = createWrapper();
expect(wrapper.findComponent(DiffLine).exists()).toBe(false);
});
it('does render a diff-line component with the correct props when there is a finding', async () => {
const wrapper = createWrapper({ props: diffCodeQuality });
wrapper.findComponent(DiffRow).vm.$emit('toggleCodeQualityFindings', 2);
await nextTick();
expect(wrapper.findComponent(DiffLine).props('line')).toBe(diffCodeQuality.diffLines[2]);
});
it('does not render a diff-line component when there is a finding and sastReportsInInlineDiff flag is true', async () => {
const wrapper = createWrapper({ props: diffCodeQuality, flag: true });
wrapper.findComponent(DiffRow).vm.$emit('toggleCodeQualityFindings', 2);
await nextTick();
expect(wrapper.findComponent(DiffLine).exists()).toBe(false);
});
it('does render a diff-line component when there is a finding and sastReportsInInlineDiff flag is false', async () => {
const wrapper = createWrapper({ props: diffCodeQuality });
wrapper.findComponent(DiffRow).vm.$emit('toggleCodeQualityFindings', 2);
await nextTick();
expect(wrapper.findComponent(DiffLine).exists()).toBe(true);
});
it.each`
type | side | container | sides | total
${'parallel'} | ${'left'} | ${'.old'} | ${{ left: { lineDrafts: [], renderDiscussion: true }, right: { lineDrafts: [], renderDiscussion: true } }} | ${2}

View File

@ -1,33 +0,0 @@
import { mountExtended } from 'helpers/vue_test_utils_helper';
import InlineFindings from '~/diffs/components/inline_findings.vue';
import DiffInlineFindings from '~/diffs/components/diff_inline_findings.vue';
import { NEW_CODE_QUALITY_FINDINGS } from '~/diffs/i18n';
import { threeCodeQualityFindings } from '../mock_data/inline_findings';
let wrapper;
const diffInlineFindings = () => wrapper.findComponent(DiffInlineFindings);
describe('InlineFindings', () => {
const createWrapper = () => {
return mountExtended(InlineFindings, {
propsData: {
codeQuality: threeCodeQualityFindings,
},
});
};
it('hides details and throws hideInlineFindings event on close click', async () => {
wrapper = createWrapper();
expect(wrapper.findByTestId('inline-findings').exists()).toBe(true);
await wrapper.findByTestId('inline-findings-close').trigger('click');
expect(wrapper.emitted('hideInlineFindings')).toHaveLength(1);
});
it('renders diff inline findings component with correct props for codequality array', () => {
wrapper = createWrapper();
expect(diffInlineFindings().props('title')).toBe(NEW_CODE_QUALITY_FINDINGS);
expect(diffInlineFindings().props('findings')).toBe(threeCodeQualityFindings);
});
});

View File

@ -1,30 +0,0 @@
import { pickDirection } from '~/diffs/utils/diff_line';
describe('diff_line utilities', () => {
describe('pickDirection', () => {
const left = {
line_code: 'left',
};
const right = {
line_code: 'right',
};
const defaultLine = {
left,
right,
};
it.each`
code | pick | line | pickDescription
${'left'} | ${left} | ${defaultLine} | ${'the left line'}
${'right'} | ${right} | ${defaultLine} | ${'the right line'}
${'junk'} | ${left} | ${defaultLine} | ${'the default: the left line'}
${'junk'} | ${right} | ${{ right }} | ${"the right line if there's no left line to default to"}
${'right'} | ${left} | ${{ left }} | ${"the left line when there isn't a right line to match"}
`(
'when provided a line and a line code `$code`, picks $pickDescription',
({ code, line, pick }) => {
expect(pickDirection({ line, code })).toBe(pick);
},
);
});
});

View File

@ -1,12 +1,12 @@
import { GlSorting, GlSortingItem } from '@gitlab/ui';
import { GlSorting } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import * as urlUtilities from '~/lib/utils/url_utility';
import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { MEMBER_TYPES, FIELD_KEY_MAX_ROLE } from '~/members/constants';
Vue.use(Vuex);
@ -47,59 +47,46 @@ describe('SortDropdown', () => {
const findSortingComponent = () => wrapper.findComponent(GlSorting);
const findSortDirectionToggle = () =>
findSortingComponent().find('button[title^="Sort direction"]');
const findDropdownToggle = () => wrapper.find('button[aria-haspopup="menu"]');
const findDropdownItemByText = (text) =>
wrapper
.findAllComponents(GlSortingItem)
.wrappers.find((dropdownItemWrapper) => dropdownItemWrapper.text() === text);
const findDropdownToggle = () => wrapper.find('button[aria-haspopup="listbox"]');
beforeEach(() => {
setWindowLocation(URL_HOST);
});
describe('dropdown options', () => {
it('adds dropdown items for all the sortable fields', () => {
it('sets sort options', () => {
const URL_FILTER_PARAMS = '?two_factor=enabled&search=foobar';
const EXPECTED_BASE_URL = `${URL_HOST}${URL_FILTER_PARAMS}&sort=`;
setWindowLocation(URL_FILTER_PARAMS);
const expectedDropdownItems = [
const expectedSortOptions = [
{
label: 'Account',
url: `${EXPECTED_BASE_URL}name_asc`,
text: 'Account',
value: 'account',
},
{
label: 'Access granted',
url: `${EXPECTED_BASE_URL}last_joined`,
text: 'Access granted',
value: 'granted',
},
{
label: 'Max role',
url: `${EXPECTED_BASE_URL}access_level_asc`,
text: 'Max role',
value: 'maxRole',
},
{
label: 'Last sign-in',
url: `${EXPECTED_BASE_URL}recent_sign_in`,
text: 'Last sign-in',
value: 'lastSignIn',
},
];
createComponent();
expectedDropdownItems.forEach((expectedDropdownItem) => {
const dropdownItem = findDropdownItemByText(expectedDropdownItem.label);
expect(dropdownItem).not.toBe(null);
expect(dropdownItem.find('a').attributes('href')).toBe(expectedDropdownItem.url);
expect(findSortingComponent().props()).toMatchObject({
text: expectedSortOptions[0].text,
isAscending: true,
sortBy: expectedSortOptions[0].value,
sortOptions: expectedSortOptions,
});
});
it('checks selected sort option', () => {
setWindowLocation('?sort=access_level_asc');
createComponent();
expect(findDropdownItemByText('Max role').vm.$attrs.active).toBe(true);
});
});
describe('dropdown toggle', () => {
@ -117,6 +104,20 @@ describe('SortDropdown', () => {
expect(findDropdownToggle().text()).toBe('Max role');
});
describe('select new sort field', () => {
beforeEach(async () => {
jest.spyOn(urlUtilities, 'visitUrl').mockImplementation();
createComponent();
findSortingComponent().vm.$emit('sortByChange', FIELD_KEY_MAX_ROLE);
await nextTick();
});
it('sorts by new field', () => {
expect(urlUtilities.visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=access_level_asc`);
});
});
});
describe('sort direction toggle', () => {

View File

@ -74,14 +74,14 @@ RSpec.describe Gitlab::Instrumentation::RedisClientMiddleware, :request_store, f
context 'when encountering exceptions' do
before do
allow(redis_client.instance_variable_get(:@raw_connection)).to receive(:call).and_raise(
RedisClient::ConnectionError, 'Connection was closed or lost')
RedisClient::Error)
end
it 'counts exception' do
expect(instrumentation_class).to receive(:instance_count_exception)
.with(instance_of(RedisClient::ConnectionError)).and_call_original
.with(instance_of(RedisClient::Error)).and_call_original
expect(instrumentation_class).to receive(:log_exception)
.with(instance_of(RedisClient::ConnectionError)).and_call_original
.with(instance_of(RedisClient::Error)).and_call_original
expect(instrumentation_class).to receive(:instance_count_request).and_call_original
expect do

View File

@ -37,20 +37,27 @@ RSpec.describe Gitlab::Instrumentation::RedisHelper, :request_store, feature_cat
subject(:minimal_test_class_instance) { MinimalTestClass.new }
describe '.instrument_call' do
let(:pipelined) { false }
let(:command) { [[:set, 'foo', 'bar']] }
subject(:instrumented_command) { minimal_test_class_instance.check_command(command, pipelined) }
it 'instruments request count' do
expect(Gitlab::Instrumentation::Redis::Cache).to receive(:instance_count_request).with(1)
expect(Gitlab::Instrumentation::Redis::Cache).not_to receive(:instance_count_pipelined_request)
minimal_test_class_instance.check_command([[:set, 'foo', 'bar']], false)
instrumented_command
end
it 'performs cluster validation' do
expect(Gitlab::Instrumentation::Redis::Cache).to receive(:redis_cluster_validate!).once
minimal_test_class_instance.check_command([[:set, 'foo', 'bar']], false)
instrumented_command
end
context 'when command is not valid for Redis Cluster' do
let(:command) { [[:mget, 'foo', 'bar']] }
before do
allow(Gitlab::Instrumentation::Redis::Cache).to receive(:redis_cluster_validate!).and_return(false)
end
@ -58,7 +65,7 @@ RSpec.describe Gitlab::Instrumentation::RedisHelper, :request_store, feature_cat
it 'reports cross slot request' do
expect(Gitlab::Instrumentation::Redis::Cache).to receive(:increment_cross_slot_request_count).once
minimal_test_class_instance.check_command([[:mget, 'foo', 'bar']], false)
instrumented_command
end
end
@ -71,21 +78,52 @@ RSpec.describe Gitlab::Instrumentation::RedisHelper, :request_store, feature_cat
end
it 'ensures duration is tracked' do
commands = [[:set, 'foo', 'bar']]
allow(Gitlab::Instrumentation::Redis::Cache).to receive(:instance_observe_duration).once
allow(Gitlab::Instrumentation::Redis::Cache).to receive(:increment_request_count).with(1).once
allow(Gitlab::Instrumentation::Redis::Cache).to receive(:add_duration).once
allow(Gitlab::Instrumentation::Redis::Cache).to receive(:add_call_details).with(anything, commands).once
allow(Gitlab::Instrumentation::Redis::Cache).to receive(:add_call_details).with(anything, command).once
expect { minimal_test_class_instance.check_command(commands, false) }.to raise_error(StandardError)
expect { instrumented_command }.to raise_error(StandardError)
end
end
context 'when a RedisClient::ConnectionError is raised' do
before do
allow(Gitlab::Instrumentation::Redis::Cache).to receive(:instance_count_request)
.and_raise(RedisClient::ConnectionError)
end
it 'silences connection errors raised during the first attempt' do
expect(Gitlab::Instrumentation::Redis::Cache).not_to receive(:log_exception).with(RedisClient::ConnectionError)
expect { instrumented_command }.to raise_error(StandardError)
expect(Thread.current[:redis_client_error_count]).to eq(1)
end
context 'when error is raised on the second attempt' do
before do
Thread.current[:redis_client_error_count] = 1
end
it 'instruments errors on second attempt' do
expect(Gitlab::Instrumentation::Redis::Cache).to receive(:log_exception).with(RedisClient::ConnectionError)
expect { instrumented_command }.to raise_error(StandardError)
expect(Thread.current[:redis_client_error_count]).to eq(2)
end
end
end
context 'when pipelined' do
let(:command) { [[:get, '{user1}:bar'], [:get, '{user1}:foo']] }
let(:pipelined) { true }
it 'instruments pipelined request count' do
expect(Gitlab::Instrumentation::Redis::Cache).to receive(:instance_count_pipelined_request)
minimal_test_class_instance.check_command([[:get, '{user1}:bar'], [:get, '{user1}:foo']], true)
instrumented_command
end
end
end

View File

@ -117,6 +117,8 @@ RSpec.describe Gitlab::Instrumentation::RedisInterceptor, :request_store, featur
expect do
redis_store_class.with { |redis| redis.call(:auth, 'foo', 'bar') }
end.to raise_exception(Redis::CommandError)
expect(Thread.current[:redis_client_error_count]).to eq(0)
end
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Patch::RedisClient, feature_category: :redis do
include RedisHelpers
let_it_be(:redis_store_class) { define_helper_redis_store_class }
let_it_be(:redis_client) { RedisClient.new(redis_store_class.redis_client_params) }
before do
Thread.current[:redis_client_error_count] = 1
end
it 'resets tracking count after each call' do
expect { redis_client.call("ping") }
.to change { Thread.current[:redis_client_error_count] }
.from(1).to(0)
end
it 'resets tracking count after each blocking call' do
expect { redis_client.blocking_call(false, "ping") }
.to change { Thread.current[:redis_client_error_count] }
.from(1).to(0)
end
it 'resets tracking count after pipelined' do
expect { redis_client.pipelined { |p| p.call("ping") } }
.to change { Thread.current[:redis_client_error_count] }
.from(1).to(0)
end
it 'resets tracking count after multi' do
expect { redis_client.multi { |p| p.call("ping") } }
.to change { Thread.current[:redis_client_error_count] }
.from(1).to(0)
end
end

View File

@ -42,7 +42,7 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :cloud_connector do
pids_killed = []
supervisor.supervise(process_ids) do |dead_pids|
pids_killed = dead_pids
pids_killed += dead_pids
[]
end
@ -60,7 +60,7 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :cloud_connector do
pids_killed = []
supervisor.supervise(process_ids) do |dead_pids|
pids_killed = dead_pids
pids_killed += dead_pids
[42] # Fake starting a new process in place of the terminated one.
end
@ -68,7 +68,7 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :cloud_connector do
Process.kill('TERM', process_ids.first)
await_condition(sleep_sec: health_check_interval_seconds) do
pids_killed == [process_ids.first]
pids_killed.include?(process_ids.first)
end
expect(Gitlab::ProcessManagement.process_alive?(process_ids.first)).to be(false)
@ -81,7 +81,7 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :cloud_connector do
pids_killed = []
supervisor.supervise(process_ids) do |dead_pids|
pids_killed = dead_pids
pids_killed += dead_pids
# Fake a new process having the same pid as one that was just terminated.
[process_ids.last]
end
@ -90,7 +90,7 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :cloud_connector do
Process.kill('TERM', process_ids.first)
await_condition(sleep_sec: health_check_interval_seconds) do
pids_killed == [process_ids.first]
pids_killed.include?(process_ids.first)
end
expect(supervisor.supervised_pids).to contain_exactly(process_ids.last)
@ -101,7 +101,7 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :cloud_connector do
pids_killed = []
supervisor.supervise(process_ids) do |dead_pids|
pids_killed = dead_pids
pids_killed += dead_pids
42
end
@ -109,7 +109,7 @@ RSpec.describe Gitlab::ProcessSupervisor, feature_category: :cloud_connector do
Process.kill('TERM', process_ids.first)
await_condition(sleep_sec: health_check_interval_seconds) do
pids_killed == [process_ids.first]
pids_killed.include?(process_ids.first)
end
expect(supervisor.supervised_pids).to contain_exactly(42, process_ids.last)

View File

@ -1,80 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_redis_shared_state do
let(:user) { build(:user, id: 1) }
let(:user2) { build(:user, id: 2) }
let(:user3) { build(:user, id: 3) }
let(:project) { build(:project) }
let(:namespace) { project.namespace }
let(:time) { Time.zone.now }
shared_examples 'tracks and counts action' do
subject { track_action(author: user, project: project) }
before do
stub_application_setting(usage_ping_enabled: true)
end
specify do
aggregate_failures do
track_action(author: user, project: project)
track_action(author: user2, project: project)
track_action(author: user3, project: project)
expect(count_unique(date_from: time.beginning_of_week, date_to: 1.week.from_now)).to eq(3)
end
end
it_behaves_like 'internal event tracking'
it 'does not track edit actions if author is not present' do
track_action(author: nil, project: project)
expect(count_unique(date_from: time.beginning_of_week, date_to: 1.week.from_now)).to eq(0)
end
end
context 'for web IDE edit actions' do
let(:event) { described_class::EDIT_BY_WEB_IDE }
it_behaves_like 'tracks and counts action' do
def track_action(params)
described_class.track_web_ide_edit_action(**params)
end
def count_unique(params)
described_class.count_web_ide_edit_actions(**params)
end
end
end
context 'for SFE edit actions' do
let(:event) { described_class::EDIT_BY_SFE }
it_behaves_like 'tracks and counts action' do
def track_action(params)
described_class.track_sfe_edit_action(**params)
end
def count_unique(params)
described_class.count_sfe_edit_actions(**params)
end
end
end
context 'for snippet editor edit actions' do
let(:event) { described_class::EDIT_BY_SNIPPET_EDITOR }
it_behaves_like 'tracks and counts action' do
def track_action(params)
described_class.track_snippet_editor_edit_action(**params)
end
def count_unique(params)
described_class.count_snippet_editor_edit_actions(**params)
end
end
end
end

View File

@ -62,15 +62,15 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe 'known_events' do
let(:weekly_event) { 'g_analytics_contribution' }
let(:daily_event) { 'g_analytics_search' }
let(:daily_event) { 'g_analytics_issues' }
let(:analytics_slot_event) { 'g_analytics_contribution' }
let(:compliance_slot_event) { 'g_compliance_dashboard' }
let(:category_analytics_event) { 'g_analytics_search' }
let(:category_analytics_event) { 'g_analytics_issues' }
let(:category_productivity_event) { 'g_analytics_productivity' }
let(:no_slot) { 'no_slot' }
let(:different_aggregation) { 'different_aggregation' }
let(:custom_daily_event) { 'g_analytics_custom' }
let(:event_overridden_for_user) { 'user_created_custom_dashboard' }
let(:global_category) { 'global' }
let(:compliance_category) { 'compliance' }
let(:productivity_category) { 'productivity' }
@ -84,7 +84,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
{ name: category_productivity_event },
{ name: compliance_slot_event },
{ name: no_slot },
{ name: different_aggregation }
{ name: different_aggregation },
{ name: event_overridden_for_user }
].map(&:with_indifferent_access)
end
@ -216,6 +217,97 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
end
end
describe "property_name" do
before do
stub_feature_flags(redis_hll_property_name_tracking: property_name_flag_enabled)
end
context "with enabled feature flag" do
let(:property_name_flag_enabled) { true }
context "with a property_name for an overridden event" do
context "with a property_name sent as a symbol" do
it "tracks the events using the Redis key override" do
expected_key = "{hll_counters}_#{event_overridden_for_user}-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(event_overridden_for_user, values: entity1, property_name: :user)
end
end
context "with a property_name sent in string format" do
it "tracks the events using the Redis key override" do
expected_key = "{hll_counters}_#{event_overridden_for_user}-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(event_overridden_for_user, values: entity1, property_name: 'user.id')
end
end
end
context "with a property_name for an overridden event that doesn't include this property_name" do
it "tracks the events using a Redis key with the property_name" do
expected_key = "{hll_counters}_#{no_slot}-user-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(no_slot, values: entity1, property_name: 'user')
end
end
context "with a property_name for a new event" do
it "tracks the events using a Redis key with the property_name" do
expected_key = "{hll_counters}_#{no_slot}-project-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(no_slot, values: entity1, property_name: 'project')
end
end
context "with no property_name for an overridden event" do
it "tracks the events using a Redis key with no property_name" do
expected_key = "{hll_counters}_#{event_overridden_for_user}-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(event_overridden_for_user, values: entity1)
end
end
context "with no property_name for a new event" do
it "tracks the events using a Redis key with no property_name" do
expected_key = "{hll_counters}_#{no_slot}-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(no_slot, values: entity1)
end
end
end
context "with disabled feature flag" do
let(:property_name_flag_enabled) { false }
it "uses old Redis key for overridden events" do
expected_key = "{hll_counters}_#{event_overridden_for_user}-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(event_overridden_for_user, values: entity1, property_name: 'user')
end
it "uses old Redis key for new events" do
expected_key = "{hll_counters}_#{no_slot}-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(no_slot, values: entity1, property_name: 'project')
end
it "uses old Redis key for new events when no property name sent" do
expected_key = "{hll_counters}_#{no_slot}-2020-23"
expect(Gitlab::Redis::HLL).to receive(:add).with(hash_including(key: expected_key))
described_class.track_event(no_slot, values: entity1)
end
end
end
end
describe '.unique_events' do
@ -227,7 +319,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
# Events last week
described_class.track_event(weekly_event, values: entity1, time: 2.days.ago)
described_class.track_event(weekly_event, values: entity1, time: 2.days.ago)
described_class.track_event(no_slot, values: entity1, time: 2.days.ago)
described_class.track_event(no_slot, values: entity1, property_name: 'user.id', time: 2.days.ago)
# Events 2 weeks ago
described_class.track_event(weekly_event, values: entity1, time: 2.weeks.ago)
@ -274,7 +366,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
context 'when no slot is set' do
it { expect(described_class.unique_events(event_names: [no_slot], start_date: 7.days.ago, end_date: Date.current)).to eq(1) }
it { expect(described_class.unique_events(event_names: [no_slot], property_name: 'user.id', start_date: 7.days.ago, end_date: Date.current)).to eq(1) }
end
context 'when data crosses into new year' do
@ -283,6 +375,97 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
.not_to raise_error
end
end
describe "property_names" do
before do
stub_feature_flags(redis_hll_property_name_tracking: property_name_flag_enabled)
end
context "with enabled feature flag" do
let(:property_name_flag_enabled) { true }
context "with a property_name for an overridden event" do
context "with a property_name sent as a symbol" do
it "tracks the events using the Redis key override" do
expected_key = "{hll_counters}_#{event_overridden_for_user}-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [event_overridden_for_user], property_name: :user, start_date: 7.days.ago, end_date: Date.current)
end
end
context "with a property_name sent in string format" do
it "tracks the events using the Redis key override" do
expected_key = "{hll_counters}_#{event_overridden_for_user}-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [event_overridden_for_user], property_name: 'user.id', start_date: 7.days.ago, end_date: Date.current)
end
end
end
context "with a property_name for an overridden event that doesn't include this property_name" do
it "tracks the events using a Redis key with the property_name" do
expected_key = "{hll_counters}_#{no_slot}-user-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [no_slot], property_name: 'user', start_date: 7.days.ago, end_date: Date.current)
end
end
context "with a property_name for a new event" do
it "tracks the events using a Redis key with the property_name" do
expected_key = "{hll_counters}_#{no_slot}-project-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [no_slot], property_name: 'project', start_date: 7.days.ago, end_date: Date.current)
end
end
context "with no property_name for a overridden event" do
it "tracks the events using a Redis key with no property_name" do
expected_key = "{hll_counters}_#{event_overridden_for_user}-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [event_overridden_for_user], start_date: 7.days.ago, end_date: Date.current)
end
end
context "with no property_name for a new event" do
it "tracks the events using a Redis key with no property_name" do
expected_key = "{hll_counters}_#{no_slot}-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [no_slot], start_date: 7.days.ago, end_date: Date.current)
end
end
end
context "with disabled feature flag" do
let(:property_name_flag_enabled) { false }
it "uses old Redis key for overridden events" do
expected_key = "{hll_counters}_#{event_overridden_for_user}-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [event_overridden_for_user], property_name: 'user', start_date: 7.days.ago, end_date: Date.current)
end
it "uses old Redis key for new events" do
expected_key = "{hll_counters}_#{no_slot}-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [no_slot], property_name: 'project', start_date: 7.days.ago, end_date: Date.current)
end
it "uses old Redis key for new events when no property name sent" do
expected_key = "{hll_counters}_#{no_slot}-2020-22"
expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [expected_key])
described_class.unique_events(event_names: [no_slot], start_date: 7.days.ago, end_date: Date.current)
end
end
end
end
describe 'key overrides file' do
@ -341,43 +524,43 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
let(:time_range) { { start_date: 7.days.ago, end_date: DateTime.current } }
let(:known_events) do
[
{ name: 'event1_slot' },
{ name: 'event2_slot' },
{ name: 'event3_slot' },
{ name: 'event5_slot' },
{ name: 'event4' }
{ name: 'g_compliance_dashboard' },
{ name: 'g_project_management_epic_created' },
{ name: 'g_project_management_epic_closed' },
{ name: 'g_project_management_epic_reopened' },
{ name: 'g_project_management_epic_issue_added' }
].map(&:with_indifferent_access)
end
before do
allow(described_class).to receive(:known_events).and_return(known_events)
described_class.track_event('event1_slot', values: entity1, time: 2.days.ago)
described_class.track_event('event1_slot', values: entity2, time: 2.days.ago)
described_class.track_event('event1_slot', values: entity3, time: 2.days.ago)
described_class.track_event('event2_slot', values: entity1, time: 2.days.ago)
described_class.track_event('event2_slot', values: entity2, time: 3.days.ago)
described_class.track_event('event2_slot', values: entity3, time: 3.days.ago)
described_class.track_event('event3_slot', values: entity1, time: 3.days.ago)
described_class.track_event('event3_slot', values: entity2, time: 3.days.ago)
described_class.track_event('event5_slot', values: entity2, time: 3.days.ago)
described_class.track_event('g_compliance_dashboard', values: entity1, time: 2.days.ago)
described_class.track_event('g_compliance_dashboard', values: entity2, time: 2.days.ago)
described_class.track_event('g_compliance_dashboard', values: entity3, time: 2.days.ago)
described_class.track_event('g_project_management_epic_created', values: entity1, time: 2.days.ago)
described_class.track_event('g_project_management_epic_created', values: entity2, time: 3.days.ago)
described_class.track_event('g_project_management_epic_created', values: entity3, time: 3.days.ago)
described_class.track_event('g_project_management_epic_closed', values: entity1, time: 3.days.ago)
described_class.track_event('g_project_management_epic_closed', values: entity2, time: 3.days.ago)
described_class.track_event('g_project_management_epic_reopened', values: entity2, time: 3.days.ago)
# events out of time scope
described_class.track_event('event2_slot', values: entity4, time: 8.days.ago)
described_class.track_event('g_project_management_epic_created', values: entity4, time: 8.days.ago)
# events in different slots
described_class.track_event('event4', values: entity1, time: 2.days.ago)
described_class.track_event('event4', values: entity2, time: 2.days.ago)
described_class.track_event('g_project_management_epic_issue_added', values: entity1, time: 2.days.ago)
described_class.track_event('g_project_management_epic_issue_added', values: entity2, time: 2.days.ago)
end
it 'calculates union of given events', :aggregate_failures do
expect(described_class.calculate_events_union(**time_range.merge(event_names: %w[event4]))).to eq 2
expect(described_class.calculate_events_union(**time_range.merge(event_names: %w[event1_slot event2_slot event3_slot]))).to eq 3
expect(described_class.calculate_events_union(**time_range.merge(event_names: %w[g_project_management_epic_issue_added]))).to eq 2
expect(described_class.calculate_events_union(**time_range.merge(event_names: %w[g_compliance_dashboard g_project_management_epic_created g_project_management_epic_closed]))).to eq 3
end
it 'returns 0 if there are no keys for given events' do
expect(Gitlab::Redis::HLL).not_to receive(:count)
expect(described_class.calculate_events_union(event_names: %w[event1_slot event2_slot event3_slot], start_date: Date.current, end_date: 4.weeks.ago)).to eq(-1)
expect(described_class.calculate_events_union(event_names: %w[g_compliance_dashboard g_project_management_epic_created g_project_management_epic_closed], start_date: Date.current, end_date: 4.weeks.ago)).to eq(-1)
end
end

View File

@ -1122,6 +1122,50 @@ RSpec.describe Group, feature_category: :groups_and_projects do
end
end
describe '.excluding_restricted_visibility_levels_for_user' do
let_it_be(:admin_user) { create(:admin) }
context 'when restricted_visibility_level is not configured' do
context 'when user is an admin', :enable_admin_mode do
it 'returns all groups' do
expect(described_class.excluding_restricted_visibility_levels_for_user(admin_user)).to eq(
[private_group, internal_group, group]
)
end
end
context 'when user is not an admin' do
it 'returns all groups' do
expect(described_class.excluding_restricted_visibility_levels_for_user(user1)).to eq(
[private_group, internal_group, group]
)
end
end
end
context 'when restricted_visibility_level is set to private' do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE])
end
context 'and user is an admin', :enable_admin_mode do
it 'returns all groups' do
expect(described_class.excluding_restricted_visibility_levels_for_user(admin_user)).to eq(
[private_group, internal_group, group]
)
end
end
context 'and user is not an admin' do
it 'excludes private groups' do
expect(described_class.excluding_restricted_visibility_levels_for_user(user1)).to eq(
[internal_group, group]
)
end
end
end
end
describe '.project_creation_allowed' do
let_it_be(:group_1) { create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS) }
let_it_be(:group_2) { create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
@ -1129,7 +1173,9 @@ RSpec.describe Group, feature_category: :groups_and_projects do
let_it_be(:group_4) { create(:group, project_creation_level: nil) }
it 'only includes groups where project creation is allowed' do
result = described_class.project_creation_allowed
expect(described_class).to receive(:excluding_restricted_visibility_levels_for_user).and_call_original
result = described_class.project_creation_allowed(user1)
expect(result).to include(group_2, group_3, group_4)
expect(result).not_to include(group_1)
@ -1141,7 +1187,9 @@ RSpec.describe Group, feature_category: :groups_and_projects do
end
it 'only includes groups where project creation is allowed' do
result = described_class.project_creation_allowed
expect(described_class).to receive(:excluding_restricted_visibility_levels_for_user).and_call_original
result = described_class.project_creation_allowed(user1)
expect(result).to include(group_2, group_3)

View File

@ -8,7 +8,31 @@ RSpec.describe Projects::BranchRule, feature_category: :source_code_management d
subject { described_class.new(protected_branch.project, protected_branch) }
describe '::find(id)' do
context 'when id matches a Project' do
it 'finds the project and initializes a branch rule' do
instance = described_class.find(protected_branch.id)
expect(instance).to be_instance_of(described_class)
expect(instance.protected_branch.id).to eq(protected_branch.id)
expect(instance.project.id).to eq(project.id)
end
end
context 'when id does not match a Project' do
it 'raises an ActiveRecord::RecordNotFound error describing the branch rule' do
expect { described_class.find(0) }.to raise_error(
ActiveRecord::RecordNotFound, "Couldn't find Projects::BranchRule with 'id'=0"
)
end
end
end
it 'generates a valid global id' do
expect(subject.to_global_id.to_s).to eq("gid://gitlab/Projects::BranchRule/#{protected_branch.id}")
end
it 'delegates methods to protected branch' do
expect(subject).to delegate_method(:id).to(:protected_branch)
expect(subject).to delegate_method(:name).to(:protected_branch)
expect(subject).to delegate_method(:group).to(:protected_branch)
expect(subject).to delegate_method(:default_branch?).to(:protected_branch)

View File

@ -581,7 +581,7 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
context 'when using access token authentication' do
it 'does not increment the usage counters' do
expect(::Gitlab::UsageDataCounters::WebIdeCounter).not_to receive(:increment_commits_count)
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_web_ide_edit_action)
expect(::Gitlab::InternalEvents).not_to receive(:track_event)
post api(url, user), params: valid_c_params
end
@ -596,21 +596,16 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
it 'increments usage counters' do
expect(::Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_commits_count)
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_web_ide_edit_action)
subject
end
it_behaves_like 'internal event tracking' do
let(:event) { ::Gitlab::UsageDataCounters::EditorUniqueCounter::EDIT_BY_WEB_IDE }
let(:event) { 'g_edit_by_web_ide' }
let(:namespace) { project.namespace.reload }
end
context 'counts.web_ide_commits Snowplow event tracking' do
before do
allow(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_web_ide_edit_action)
end
it_behaves_like 'Snowplow event tracking' do
let(:action) { :commit }
let(:category) { described_class.to_s }

View File

@ -106,7 +106,9 @@ RSpec.describe 'Creating a Snippet', feature_category: :source_code_management d
end
context 'with PersonalSnippet' do
it_behaves_like 'creates snippet'
it_behaves_like 'creates snippet' do
let(:project) { nil }
end
end
context 'with ProjectSnippet' do

View File

@ -43,7 +43,8 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d
shared_examples 'graphql update actions' do
context 'when the user does not have permission' do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
let(:current_user) { user }
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
@ -131,14 +132,18 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d
it_behaves_like 'graphql update actions'
it_behaves_like 'when the snippet is not found'
it_behaves_like 'snippet edit usage data counters'
it_behaves_like 'snippet edit usage data counters' do
let(:user) { current_user }
end
it_behaves_like 'has spam protection' do
let(:mutation_class) { ::Mutations::Snippets::Update }
end
end
describe 'ProjectSnippet' do
let_it_be(:project) { create(:project, :private) }
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project) { create(:project, :private, namespace: namespace) }
let(:snippet) do
create(
@ -181,7 +186,9 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d
end
end
it_behaves_like 'snippet edit usage data counters'
it_behaves_like 'snippet edit usage data counters' do
let(:user) { current_user }
end
it_behaves_like 'has spam protection' do
let(:mutation_class) { ::Mutations::Snippets::Update }
@ -193,9 +200,8 @@ RSpec.describe 'Updating a Snippet', feature_category: :source_code_management d
end
it_behaves_like 'internal event tracking' do
let(:event) { ::Gitlab::UsageDataCounters::EditorUniqueCounter::EDIT_BY_SNIPPET_EDITOR }
let(:event) { 'g_edit_by_snippet_ide' }
let(:user) { current_user }
let(:namespace) { project.namespace }
end
end
end

View File

@ -330,6 +330,9 @@ RSpec.configure do |config|
# Postgres is the primary data source, and ClickHouse only when enabled in certain cases.
stub_feature_flags(clickhouse_data_collection: false)
# This is going to be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/432866
stub_feature_flags(redis_hll_property_name_tracking: false)
else
unstub_all_feature_flags
end

View File

@ -125,9 +125,6 @@ project_compliance_standards_adherence:
project_repositories:
index_project_repositories_on_shard_id_and_project_id:
- index_project_repositories_on_shard_id
protected_environments:
index_protected_environments_on_project_id_and_name:
- index_protected_environments_on_project_id
protected_tags:
index_protected_tags_on_project_id_and_name:
- index_protected_tags_on_project_id

View File

@ -6727,7 +6727,6 @@
- './spec/lib/gitlab/usage_data_counters/code_review_events_spec.rb'
- './spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/designs_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/editor_unique_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/gitlab_cli_activity_unique_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb'
- './spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb'

View File

@ -23,7 +23,7 @@ RSpec.shared_examples 'internal event tracking' do
project = try(:project)
user = try(:user)
namespace = try(:namespace)
namespace = try(:namespace) || project&.namespace
expect(Gitlab::Tracking::StandardContext)
.to have_received(:new)

View File

@ -14,7 +14,7 @@ RSpec.shared_examples 'snippet edit usage data counters' do
context 'when user is sessionless' do
it 'does not track usage data actions' do
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_snippet_editor_edit_action)
expect(::Gitlab::InternalEvents).not_to receive(:track_event)
post_graphql_mutation(mutation, current_user: current_user)
end
@ -25,17 +25,19 @@ RSpec.shared_examples 'snippet edit usage data counters' do
stub_session('warden.user.user.key' => [[current_user.id], current_user.authenticatable_salt])
end
it 'tracks usage data actions', :clean_gitlab_redis_sessions do
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_snippet_editor_edit_action)
subject do
post_graphql_mutation(mutation)
end
it_behaves_like 'internal event tracking' do
let(:event) { 'g_edit_by_snippet_ide' }
end
context 'when mutation result raises an error' do
it 'does not track usage data actions' do
mutation_vars[:title] = nil
expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_snippet_editor_edit_action)
expect(::Gitlab::InternalEvents).not_to receive(:track_event)
post_graphql_mutation(mutation)
end