Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-01-22 21:08:53 +00:00
parent a9a2f9257e
commit 386dcdbe9d
59 changed files with 1053 additions and 149 deletions

View File

@ -115,6 +115,14 @@ include:
--input-files "rspec/rspec-*.json" \
--merge_request_iid "$CI_MERGE_REQUEST_IID";
fi
if [ "$ALLOW_KNAPSACK_REPORT_CREATE_ISSUES" == "true" ]; then
bundle exec knapsack-report-issues \
--token "${KNAPSACK_REPORT_ISSUES_PROJECT_TOKEN}" \
--project "gitlab-org/gitlab" \
--input-file knapsack/rspec_*_report.json \
--expected-report node_specs_expected_duration.json \
--merge_request_iid "$CI_MERGE_REQUEST_IID";
fi
- echo -e "\e[0Ksection_end:`date +%s`:report_results_section\r\e[0K"
- tooling/bin/push_job_metrics || true

View File

@ -1 +1 @@
3.2.2
3.2.3

View File

@ -518,7 +518,7 @@ group :test do
# Moved in `test` because https://gitlab.com/gitlab-org/gitlab/-/issues/217527
gem 'derailed_benchmarks', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'gitlab_quality-test_tooling', '~> 1.12.0', require: false, feature_category: :tooling
gem 'gitlab_quality-test_tooling', '~> 1.13.0', require: false, feature_category: :tooling
end
gem 'octokit', '~> 6.0' # rubocop:todo Gemfile/MissingFeatureCategory

View File

@ -226,7 +226,7 @@
{"name":"gitlab-styles","version":"11.0.0","platform":"ruby","checksum":"0dd8ec066ce9955ac51d3616c6bfded30f75bb526f39ff392ece6f43d5b9406b"},
{"name":"gitlab_chronic_duration","version":"0.12.0","platform":"ruby","checksum":"0d766944d415b5c831f176871ee8625783fc0c5bfbef2d79a3a616f207ffc16d"},
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
{"name":"gitlab_quality-test_tooling","version":"1.12.0","platform":"ruby","checksum":"6d687db96777bd8c87264253a7cc9ce1d71851d2b20a64d594770c35555630b1"},
{"name":"gitlab_quality-test_tooling","version":"1.13.0","platform":"ruby","checksum":"04c51c0b372a38e030572ab85b997234e139d0d639d617ce1924269021b51328"},
{"name":"globalid","version":"1.1.0","platform":"ruby","checksum":"b337e1746f0c8cb0a6c918234b03a1ddeb4966206ce288fbb57779f59b2d154f"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-apis-androidpublisher_v3","version":"0.34.0","platform":"ruby","checksum":"d7e1d7dd92f79c498fe2082222a1740d788e022e660c135564b3fd299cab5425"},
@ -572,7 +572,7 @@
{"name":"ruby-saml","version":"1.15.0","platform":"ruby","checksum":"3a9dda2b448310f4f90d5cf0967d4b668530fa7994d2a4d9cbfdfa62e35f76a3"},
{"name":"ruby-statistics","version":"3.0.0","platform":"ruby","checksum":"610301370346931cb701e3a8d3d3e28eb65681162cae6066c0c11abf20efdc81"},
{"name":"ruby2_keywords","version":"0.0.5","platform":"ruby","checksum":"ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef"},
{"name":"ruby_parser","version":"3.20.3","platform":"ruby","checksum":"8d2289a695dc81ffddcdd5a56e80c9a109806bc0d0b1239a1c852b0c71251c49"},
{"name":"ruby_parser","version":"3.21.0","platform":"ruby","checksum":"3842893d2f4602dcd93c0a79d77f9ce8e1ce197d41ac533d8e25c684f8f1c56b"},
{"name":"rubyntlm","version":"0.6.3","platform":"ruby","checksum":"5b321456dba3130351f7451f8669f1afa83a0d26fd63cdec285b7b88e667102d"},
{"name":"rubypants","version":"0.2.0","platform":"ruby","checksum":"f07e38eac793655a0323fe91946081052341b9e69807026fcf102346589eedee"},
{"name":"rubyzip","version":"2.3.2","platform":"ruby","checksum":"3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f"},
@ -593,7 +593,7 @@
{"name":"sentry-ruby","version":"5.10.0","platform":"ruby","checksum":"115c24c0aee1309210f3a2988fb118e2bec1f11609feeda90e694388b1183619"},
{"name":"sentry-sidekiq","version":"5.10.0","platform":"ruby","checksum":"cc81018d0733fb1be3fb5641c9e0b61030bbeaa1d0b23ca64797d70def7aea1a"},
{"name":"set","version":"1.0.2","platform":"ruby","checksum":"02ffa4de1f2621495e05b72326040dd014d7abbcb02fea698bc600a389992c02"},
{"name":"sexp_processor","version":"4.17.0","platform":"ruby","checksum":"4daa4874ce1838cd801c65e66ed5d4f140024404a3de7482c36d4ef2604dff6f"},
{"name":"sexp_processor","version":"4.17.1","platform":"ruby","checksum":"91110946720307f30bf1d549e90d9a529fef40d1fc471c069c8cca7667015da0"},
{"name":"shellany","version":"0.0.1","platform":"ruby","checksum":"0e127a9132698766d7e752e82cdac8250b6adbd09e6c0a7fbbb6f61964fedee7"},
{"name":"shoulda-matchers","version":"5.1.0","platform":"ruby","checksum":"a01d20589989e9653ab4a28c67d9db2b82bcf0a2496cf01d5e1a95a4aaaf5b07"},
{"name":"sidekiq","version":"7.1.6","platform":"ruby","checksum":"7859da66d5bcef3c22bea2c3091d08c866890168e003f5bf4dea197dc37843a2"},

View File

@ -732,7 +732,7 @@ GEM
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
gitlab_quality-test_tooling (1.12.0)
gitlab_quality-test_tooling (1.13.0)
activesupport (>= 6.1, < 7.2)
amatch (~> 0.4.1)
gitlab (~> 4.19)
@ -1500,7 +1500,8 @@ GEM
rexml
ruby-statistics (3.0.0)
ruby2_keywords (0.0.5)
ruby_parser (3.20.3)
ruby_parser (3.21.0)
racc (~> 1.5)
sexp_processor (~> 4.16)
rubyntlm (0.6.3)
rubypants (0.2.0)
@ -1547,7 +1548,7 @@ GEM
sentry-ruby (~> 5.10.0)
sidekiq (>= 3.0)
set (1.0.2)
sexp_processor (4.17.0)
sexp_processor (4.17.1)
shellany (0.0.1)
shoulda-matchers (5.1.0)
activesupport (>= 5.2.0)
@ -1914,7 +1915,7 @@ DEPENDENCIES
gitlab-utils!
gitlab_chronic_duration (~> 0.12)
gitlab_omniauth-ldap (~> 2.2.0)
gitlab_quality-test_tooling (~> 1.12.0)
gitlab_quality-test_tooling (~> 1.13.0)
gon (~> 6.4.0)
google-apis-androidpublisher_v3 (~> 0.34.0)
google-apis-cloudbilling_v1 (~> 0.21.0)

View File

@ -15,6 +15,7 @@ import {
MR_COMMITS_PREVIOUS_COMMIT,
} from '~/behaviors/shortcuts/keybindings';
import { createAlert } from '~/alert';
import { InternalEvents } from '~/tracking';
import { isSingleViewStyle } from '~/helpers/diffs_helper';
import { helpPagePath } from '~/helpers/help_page_helper';
import { parseBoolean, handleLocationHash } from '~/lib/utils/common_utils';
@ -86,7 +87,7 @@ export default {
GlSprintf,
GlAlert,
},
mixins: [glFeatureFlagsMixin()],
mixins: [glFeatureFlagsMixin(), InternalEvents.mixin()],
alerts: {
ALERT_OVERFLOW_HIDDEN,
ALERT_MERGE_CONFLICT,
@ -443,6 +444,8 @@ export default {
notesEventHub.$once('fetchDiffData', this.fetchData);
notesEventHub.$on('refetchDiffData', this.refetchDiffData);
notesEventHub.$on('fetchedNotesData', this.rereadNoteHash);
notesEventHub.$on('noteFormAddToReview', this.handleReviewTracking);
notesEventHub.$on('noteFormStartReview', this.handleReviewTracking);
diffsEventHub.$on('diffFilesModified', this.setDiscussions);
diffsEventHub.$on('doneLoadingBatches', this.autoScroll);
diffsEventHub.$on(EVT_MR_PREPARED, this.fetchData);
@ -453,6 +456,8 @@ export default {
diffsEventHub.$off(EVT_MR_PREPARED, this.fetchData);
diffsEventHub.$off('doneLoadingBatches', this.autoScroll);
diffsEventHub.$off('diffFilesModified', this.setDiscussions);
notesEventHub.$off('noteFormStartReview', this.handleReviewTracking);
notesEventHub.$off('noteFormAddToReview', this.handleReviewTracking);
notesEventHub.$off('fetchedNotesData', this.rereadNoteHash);
notesEventHub.$off('refetchDiffData', this.refetchDiffData);
notesEventHub.$off('fetchDiffData', this.fetchData);
@ -679,6 +684,16 @@ export default {
reloadPage() {
window.location.reload();
},
handleReviewTracking(event) {
const types = {
noteFormStartReview: 'merge_request_click_start_review_on_changes_tab',
noteFormAddToReview: 'merge_request_click_add_to_review_on_changes_tab',
};
if (this.shouldShow && types[event.name]) {
this.trackEvent(types[event.name]);
}
},
},
howToMergeDocsPath: helpPagePath('user/project/merge_requests/reviews/index.md', {
anchor: 'checkout-merge-requests-locally-through-the-head-ref',

View File

@ -401,6 +401,8 @@ export const nWeeksBefore = (date, numberOfWeeks, options) =>
/**
* Returns the date `n` years after the date provided.
* When Feb 29 is the specified date, the default behaviour is to return March 1.
* But to align with the equivalent rails code, moment JS and datefns we should return Feb 28 instead.
*
* @param {Date} date the initial date
* @param {Number} numberOfYears number of years after
@ -408,7 +410,16 @@ export const nWeeksBefore = (date, numberOfWeeks, options) =>
*/
export const nYearsAfter = (date, numberOfYears) => {
const clone = newDate(date);
clone.setFullYear(clone.getFullYear() + numberOfYears);
clone.setUTCMonth(clone.getUTCMonth());
// If the date we are calculating from is Feb 29, return the equivalent result for Feb 28
if (clone.getUTCMonth() === 1 && clone.getUTCDate() === 29) {
clone.setUTCDate(28);
} else {
clone.setUTCDate(clone.getUTCDate());
}
clone.setUTCFullYear(clone.getUTCFullYear() + numberOfYears);
return clone;
};

View File

@ -12,6 +12,7 @@ import {
slugifyWithUnderscore,
} from '~/lib/utils/text_utility';
import { sprintf } from '~/locale';
import { InternalEvents } from '~/tracking';
import { badgeState } from '~/merge_requests/components/merge_request_header.vue';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
@ -48,7 +49,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [issuableStateMixin],
mixins: [issuableStateMixin, InternalEvents.mixin()],
props: {
noteableType: {
type: String,
@ -253,6 +254,10 @@ export default {
this.isSubmitting = true;
if (isDraft) {
eventHub.$emit('noteFormAddToReview', { name: 'noteFormAddToReview' });
}
trackSavedUsingEditor(
this.$refs.markdownEditor.isContentEditorActive,
`${this.noteableType}_${this.noteType}`,

View File

@ -320,12 +320,14 @@ export default {
);
},
handleAddToReview() {
const clickType = this.hasDrafts ? 'noteFormAddToReview' : 'noteFormStartReview';
// check if draft should resolve thread
const shouldResolve =
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving);
this.isSubmitting = true;
eventHub.$emit(clickType, { name: clickType });
this.$emit(
'handleFormUpdateAddToReview',
this.updatedNoteBody,

View File

@ -2,6 +2,7 @@
// eslint-disable-next-line no-restricted-imports
import { mapGetters, mapActions } from 'vuex';
import { v4 as uuidv4 } from 'uuid';
import { InternalEvents } from '~/tracking';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
@ -39,7 +40,7 @@ export default {
TimelineEntryItem,
AiSummary: () => import('ee_component/notes/components/ai_summary.vue'),
},
mixins: [glFeatureFlagsMixin()],
mixins: [glFeatureFlagsMixin(), InternalEvents.mixin()],
provide() {
return {
summarizeClientSubscriptionId: uuidv4(),
@ -165,6 +166,9 @@ export default {
});
}
eventHub.$on('noteFormAddToReview', this.handleReviewTracking);
eventHub.$on('noteFormStartReview', this.handleReviewTracking);
window.addEventListener('hashchange', this.handleHashChanged);
eventHub.$on('notesApp.updateIssuableConfidentiality', this.setConfidentiality);
@ -177,6 +181,8 @@ export default {
beforeDestroy() {
window.removeEventListener('hashchange', this.handleHashChanged);
eventHub.$off('notesApp.updateIssuableConfidentiality', this.setConfidentiality);
eventHub.$off('noteFormStartReview', this.handleReviewTracking);
eventHub.$off('noteFormAddToReview', this.handleReviewTracking);
},
methods: {
...mapActions([
@ -222,6 +228,16 @@ export default {
setAiLoading(loading) {
this.aiLoading = loading;
},
handleReviewTracking(event) {
const types = {
noteFormStartReview: 'merge_request_click_start_review_on_overview_tab',
noteFormAddToReview: 'merge_request_click_add_to_review_on_overview_tab',
};
if (this.shouldShow && window.mrTabs && types[event.name]) {
this.trackEvent(types[event.name]);
}
},
},
systemNote: constants.SYSTEM_NOTE,
};

View File

@ -1,20 +1,23 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { GlAlert, GlKeysetPagination } from '@gitlab/ui';
import StorageUsageStatistics from 'ee_else_ce/usage_quotas/storage/components/storage_usage_statistics.vue';
import SearchAndSortBar from '~/usage_quotas/components/search_and_sort_bar/search_and_sort_bar.vue';
import DependencyProxyUsage from './dependency_proxy_usage.vue';
import ContainerRegistryUsage from './container_registry_usage.vue';
import ProjectList from './project_list.vue';
export default {
name: 'NamespaceStorageApp',
components: {
GlAlert,
GlKeysetPagination,
StorageUsageStatistics,
DependencyProxyUsage,
ContainerRegistryUsage,
SearchAndSortBar,
ProjectList,
},
inject: ['userNamespace', 'namespaceId'],
inject: ['userNamespace', 'namespaceId', 'helpLinks', 'defaultPerPage'],
props: {
namespaceLoadingError: {
type: Boolean,
@ -31,11 +34,26 @@ export default {
required: false,
default: false,
},
isNamespaceProjectsLoading: {
type: Boolean,
required: false,
default: false,
},
namespace: {
type: Object,
required: false,
default: () => ({}),
},
projects: {
type: Object,
required: false,
default: () => ({}),
},
initialSortBy: {
type: String,
required: false,
default: 'storage',
},
},
computed: {
usedStorage() {
@ -55,6 +73,27 @@ export default {
containerRegistrySizeIsEstimated() {
return this.namespace.rootStorageStatistics?.containerRegistrySizeIsEstimated ?? false;
},
projectList() {
return this.projects?.nodes ?? [];
},
pageInfo() {
return this.projects?.pageInfo;
},
showPagination() {
return Boolean(this.pageInfo?.hasPreviousPage || this.pageInfo?.hasNextPage);
},
},
methods: {
onPrev(before) {
if (this.pageInfo?.hasPreviousPage) {
this.$emit('fetch-more-projects', { before, last: this.defaultPerPage, first: undefined });
}
},
onNext(after) {
if (this.pageInfo?.hasNextPage) {
this.$emit('fetch-more-projects', { after, first: this.defaultPerPage });
}
},
},
};
</script>
@ -103,7 +142,26 @@ export default {
"
/>
</div>
<slot name="ee-storage-app"></slot>
<project-list
:projects="projectList"
:is-loading="isNamespaceProjectsLoading"
:help-links="helpLinks"
:sort-by="initialSortBy"
:sort-desc="true"
@sortChanged="
($event) => {
$emit('sort-changed', $event);
}
"
/>
<div class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination
v-if="showPagination"
v-bind="pageInfo"
@prev="onPrev"
@next="onNext"
/>
</div>
</section>
</div>
</template>

View File

@ -0,0 +1,208 @@
<script>
import { GlTable, GlLink, GlSprintf, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import { containerRegistryPopover } from '~/usage_quotas/storage/constants';
import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
import HelpPageLink from '~/vue_shared/components/help_page_link/help_page_link.vue';
import StorageTypeHelpLink from './storage_type_help_link.vue';
import StorageTypeWarning from './storage_type_warning.vue';
export default {
name: 'ProjectList',
components: {
GlTable,
GlLink,
GlSprintf,
GlIcon,
ProjectAvatar,
NumberToHumanSize,
HelpPageLink,
StorageTypeHelpLink,
StorageTypeWarning,
},
inject: ['isUsingProjectEnforcementWithLimits'],
props: {
projects: {
type: Array,
required: true,
},
isLoading: {
type: Boolean,
required: true,
},
helpLinks: {
type: Object,
required: true,
},
sortBy: {
type: String,
required: false,
default: undefined,
},
sortDesc: {
type: Boolean,
required: false,
default: undefined,
},
},
created() {
this.fields = [
{ key: 'name', label: __('Project') },
{ key: 'storage', label: __('Total'), sortable: !this.isUsingProjectEnforcementWithLimits },
{ key: 'repository', label: __('Repository') },
{ key: 'snippets', label: __('Snippets') },
{ key: 'buildArtifacts', label: __('Jobs') },
{ key: 'lfsObjects', label: __('LFS') },
{ key: 'packages', label: __('Packages') },
{ key: 'wiki', label: __('Wiki') },
{
key: 'containerRegistry',
label: __('Containers'),
thClass: 'gl-border-l!',
tdClass: 'gl-border-l!',
},
].map((f) => ({
...f,
// eslint-disable-next-line @gitlab/require-i18n-strings
thClass: `${f.thClass ?? ''} gl-px-3!`,
// eslint-disable-next-line @gitlab/require-i18n-strings
tdClass: `${f.tdClass ?? ''} gl-px-3!`,
}));
},
methods: {
/**
* Builds a gl-table td cell slot name for particular field
* @param {string} key
* @returns {string} */
getHeaderSlotName(key) {
return `head(${key})`;
},
getUsageQuotasUrl(projectUrl) {
return `${projectUrl}/-/usage_quotas`;
},
/**
* Creates a relative path from a full project path.
* E.g. input `namespace / subgroup / project`
* results in `subgroup / project`
*/
getProjectRelativePath(fullPath) {
return fullPath.replace(/.*?\s?\/\s?/, '');
},
isCostFactored(project) {
return project.statistics.storageSize !== project.statistics.costFactoredStorageSize;
},
},
containerRegistryPopover,
};
</script>
<template>
<gl-table
:fields="fields"
:items="projects"
:busy="isLoading"
show-empty
:empty-text="s__('UsageQuota|No projects to display.')"
small
stacked="lg"
:sort-by="sortBy"
:sort-desc="sortDesc"
no-local-sorting
no-sort-reset
@sort-changed="$emit('sortChanged', $event)"
>
<template v-for="field in fields" #[getHeaderSlotName(field.key)]>
<div :key="field.key" :data-testid="'th-' + field.key">
{{ field.label }}
<storage-type-help-link
v-if="field.key in helpLinks"
:storage-type="field.key"
:help-links="helpLinks"
/><storage-type-warning v-if="field.key == 'containerRegistry'">
{{ $options.containerRegistryPopover.content }}
<gl-link :href="$options.containerRegistryPopover.docsLink" target="_blank">
{{ __('Learn more.') }}
</gl-link>
</storage-type-warning>
</div>
</template>
<template #cell(name)="{ item: project }">
<project-avatar
:project-id="project.id"
:project-name="project.name"
:project-avatar-url="project.avatarUrl"
:size="16"
:alt="project.name"
class="gl-display-inline-block gl-mr-2 gl-text-center!"
/>
<gl-link
:href="getUsageQuotasUrl(project.webUrl)"
class="gl-text-gray-900! js-project-link gl-word-break-word"
data-testid="project-link"
>
{{ getProjectRelativePath(project.nameWithNamespace) }}
</gl-link>
</template>
<template #cell(storage)="{ item: project }">
<template v-if="isCostFactored(project)">
<number-to-human-size :value="project.statistics.costFactoredStorageSize" />
<div class="gl-text-gray-600 gl-mt-2 gl-font-sm">
<gl-sprintf :message="s__('UsageQuotas|(of %{totalStorageSize})')">
<template #totalStorageSize>
<number-to-human-size :value="project.statistics.storageSize" />
</template>
</gl-sprintf>
<help-page-link href="user/usage_quotas#view-project-fork-storage-usage" target="_blank">
<gl-icon name="question-o" :size="12" />
</help-page-link>
</div>
</template>
<template v-else>
<number-to-human-size :value="project.statistics.storageSize" />
</template>
</template>
<template #cell(repository)="{ item: project }">
<number-to-human-size
:value="project.statistics.repositorySize"
data-testid="project-repository-size"
/>
</template>
<template #cell(lfsObjects)="{ item: project }">
<number-to-human-size :value="project.statistics.lfsObjectsSize" />
</template>
<template #cell(buildArtifacts)="{ item: project }">
<number-to-human-size :value="project.statistics.buildArtifactsSize" />
</template>
<template #cell(packages)="{ item: project }">
<number-to-human-size :value="project.statistics.packagesSize" />
</template>
<template #cell(wiki)="{ item: project }">
<number-to-human-size :value="project.statistics.wikiSize" data-testid="project-wiki-size" />
</template>
<template #cell(snippets)="{ item: project }">
<number-to-human-size
:value="project.statistics.snippetsSize"
data-testid="project-snippets-size"
/>
</template>
<template #cell(containerRegistry)="{ item: project }">
<number-to-human-size
:value="project.statistics.containerRegistrySize"
data-testid="project-containers-registry-size"
/>
</template>
</gl-table>
</template>

View File

@ -0,0 +1,36 @@
<script>
import { GlLink, GlIcon } from '@gitlab/ui';
import { sprintf } from '~/locale';
import { HELP_LINK_ARIA_LABEL } from '~/usage_quotas/storage/constants';
export default {
name: 'StorageTypeHelpLink',
components: {
GlLink,
GlIcon,
},
props: {
storageType: {
type: String,
required: true,
},
helpLinks: {
type: Object,
required: true,
},
},
computed: {
ariaLabel() {
return sprintf(HELP_LINK_ARIA_LABEL, {
linkTitle: this.storageType,
});
},
},
};
</script>
<template>
<gl-link :href="helpLinks[storageType]" target="_blank" :aria-label="ariaLabel">
<gl-icon name="question-o" :size="12" />
</gl-link>
</template>

View File

@ -16,7 +16,7 @@ module ServicePing
end
def execute
return unless ServicePing::ServicePingSettings.product_intelligence_enabled?
return unless ServicePing::ServicePingSettings.enabled_and_consented?
start_time = Time.current

View File

@ -0,0 +1,20 @@
---
description: User clicks the "Add to review" button on the Merge Request Changes tab
category: InternalEventTracking
action: merge_request_click_add_to_review_on_changes_tab
identifiers:
- project
- namespace
- user
product_section: dev
product_stage: create
product_group: code_review
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
description: User clicks the "Add to review" button on the Merge Request Overview tab
category: InternalEventTracking
action: merge_request_click_add_to_review_on_overview_tab
identifiers:
- project
- namespace
- user
product_section: dev
product_stage: create
product_group: code_review
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
description: User clicks the "Start a review" button on the Merge Request Changes tab
category: InternalEventTracking
action: merge_request_click_start_review_on_changes_tab
identifiers:
- project
- namespace
- user
product_section: dev
product_stage: create
product_group: code_review
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
---
description: User clicks the "Start a review" button on the Merge Request Overview tab
category: InternalEventTracking
action: merge_request_click_start_review_on_overview_tab
identifiers:
- project
- namespace
- user
product_section: dev
product_stage: create
product_group: code_review
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -1,9 +0,0 @@
---
name: github_importer_attachments
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/436400
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140524
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17318
milestone: '16.8'
group: group::import and integrate
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_add_to_review_on_changes_tab_monthly
description: Monthly count of clicks on the Add to review button on the MR Changes tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_add_to_review_on_changes_tab
events:
- name: merge_request_click_add_to_review_on_changes_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_add_to_review_on_overview_tab_monthly
description: Monthly count of clicks on the Add to review button on the MR Overview tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_add_to_review_on_overview_tab
events:
- name: merge_request_click_add_to_review_on_overview_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_start_review_on_changes_tab_monthly
description: Monthly count of clicks on the Start a Review button on the MR Changes tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_start_review_on_changes_tab
events:
- name: merge_request_click_start_review_on_changes_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_start_review_on_overview_tab_monthly
description: Monthly count of clicks on the Start a Review button on the MR Overview tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: 28d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_start_review_on_overview_tab
events:
- name: merge_request_click_start_review_on_overview_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_add_to_review_on_changes_tab_weekly
description: Weekly count of clicks on the Add to review button on the MR Changes tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_add_to_review_on_changes_tab
events:
- name: merge_request_click_add_to_review_on_changes_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_add_to_review_on_overview_tab_weekly
description: Weekly count of clicks on the Add to review button on the MR Overview tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_add_to_review_on_overview_tab
events:
- name: merge_request_click_add_to_review_on_overview_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_start_review_on_changes_tab_weekly
description: Weekly count of clicks on the Start a Review button on the MR Changes tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_start_review_on_changes_tab
events:
- name: merge_request_click_start_review_on_changes_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_start_review_on_overview_tab_weekly
description: Weekly count of clicks on the Start a Review button on the MR Overview tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: 7d
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_start_review_on_overview_tab
events:
- name: merge_request_click_start_review_on_overview_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_add_to_review_on_changes_tab
description: Total count of clicks on the Add to review button on the MR Changes tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: all
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_add_to_review_on_changes_tab
events:
- name: merge_request_click_add_to_review_on_changes_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_add_to_review_on_overview_tab
description: Total count of clicks on the Add to review button on the MR Overview tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: all
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_add_to_review_on_overview_tab
events:
- name: merge_request_click_add_to_review_on_overview_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_start_review_on_changes_tab
description: Total count of clicks on the Start a Review button on the MR Changes tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: all
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_start_review_on_changes_tab
events:
- name: merge_request_click_start_review_on_changes_tab

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_merge_request_click_start_review_on_overview_tab
description: Total count of clicks on the Start a Review button on the MR Overview tab
product_section: dev
product_stage: create
product_group: code_review
performance_indicator_type: []
value_type: number
status: active
milestone: '16.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141911
time_frame: all
data_source: internal_events
data_category: optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- merge_request_click_start_review_on_overview_tab
events:
- name: merge_request_click_start_review_on_overview_tab

View File

@ -13,6 +13,9 @@ Resource state events keep track of what happens to GitLab [issues](../user/proj
Use them to track which state was set, who did it, and when it happened.
Resource state events API does not track the initial state ("create" or "open") of resources.
For a resource that was not closed or re-opened, an empty list is returned.
## Issues
### List project issue state events

View File

@ -32,9 +32,8 @@ In the Markdown doc for a resource (AKA endpoint):
- Every method must have a detailed [description of the response body](#response-body-description).
- Every method must have a response body example (in JSON format).
- If an attribute is available only to higher level tiers than the other
attributes, add the appropriate inline [tier badge](styleguide/index.md#product-tier-badges).
Put the badge in the **Attribute** column, like the
`**(<tier>)**` code in the following template.
attributes, add the appropriate tier to the description. If an attribute is
for Premium, include that it's also available for Ultimate.
After a new API documentation page is added, [add an entry in the global navigation](site_architecture/global_nav.md#add-a-navigation-entry). [Example](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/3497).
@ -65,7 +64,7 @@ Supported attributes:
| Attribute | Type | Required | Description |
|--------------------------|----------|----------|-----------------------|
| `attribute` | datatype | Yes | Detailed description. |
| `attribute` **(<tier>)** | datatype | No | Detailed description. |
| `attribute` | datatype | No | Detailed description. |
| `attribute` | datatype | No | Detailed description. |
| `attribute` | datatype | No | Detailed description. |
@ -75,7 +74,7 @@ response attributes:
| Attribute | Type | Description |
|--------------------------|----------|-----------------------|
| `attribute` | datatype | Detailed description. |
| `attribute` **(<tier>)** | datatype | Detailed description. |
| `attribute` | datatype | Detailed description. |
Example request:
@ -146,7 +145,7 @@ Sort the table by required attributes first, then alphabetically.
| Attribute | Type | Required | Description |
|------------------------------|---------------|----------|-----------------------------------------------------|
| `title` | string | Yes | Title of the issue. |
| `assignee_ids` **(PREMIUM ALL)** | integer array | No | IDs of the users to assign the issue to. |
| `assignee_ids` | integer array | No | IDs of the users to assign the issue to. Ultimate only. |
| `confidential` | boolean | No | Sets the issue to confidential. Default is `false`. |
```
@ -155,7 +154,7 @@ Rendered example:
| Attribute | Type | Required | Description |
|------------------------------|---------------|----------|-----------------------------------------------------|
| `title` | string | Yes | Title of the issue. |
| `assignee_ids` **(PREMIUM ALL)** | integer array | No | IDs of the users to assign the issue to. |
| `assignee_ids` | integer array | No | IDs of the users to assign the issue to. Premium and Ultimate only. |
| `confidential` | boolean | No | Sets the issue to confidential. Default is `false`. |
For information about writing attribute descriptions, see the [GraphQL API description style guide](../api_graphql_styleguide.md#description-style-guide).
@ -181,7 +180,7 @@ Sort the table alphabetically.
```markdown
| Attribute | Type | Description |
|------------------------------|---------------|-------------------------------------------|
| `assignee_ids` **(PREMIUM ALL)** | integer array | IDs of the users to assign the issue to. |
| `assignee_ids` | integer array | IDs of the users to assign the issue to. Premium and Ultimate only. |
| `confidential` | boolean | Whether the issue is confidential or not. |
| `title` | string | Title of the issue. |
```
@ -190,7 +189,7 @@ Rendered example:
| Attribute | Type | Description |
|------------------------------|---------------|-------------------------------------------|
| `assignee_ids` **(PREMIUM ALL)** | integer array | IDs of the users to assign the issue to. |
| `assignee_ids` | integer array | IDs of the users to assign the issue to. Premium and Ultimate only. |
| `confidential` | boolean | Whether the issue is confidential or not. |
| `title` | string | Title of the issue. |

View File

@ -1648,7 +1648,7 @@ Tier badges provide information about a feature and are displayed next to the to
Assign tier badges to:
- All H1 topic titles, except the pages under `doc/development/*` and `doc/solutions/*`.
- Most H1 topic titles, except the pages under `doc/development/*` and `doc/solutions/*`.
- Topic titles that don't apply to the same tier as the H1.
The H1 tier badge should be the badge that applies to the lowest tier for the features on the page.
@ -1731,10 +1731,19 @@ Or add the status by itself:
##### Inline tier badges
Do not add tier badges inline with other text, except for [API attributes](../restful_api_styleguide.md).
Do not add tier badges inline with other text.
The single source of truth for a feature should be the topic where the
functionality is described.
If you need to mention a tier inline, write it in plain text. For example,
for an API topic:
```markdown
IDs of the users to assign the issue to. Ultimate only.
```
For more examples, see the [REST API style guide](../restful_api_styleguide.md).
##### Administrator documentation tier badges
Topics that are only for instance administrators should be badged `<TIER> SELF`. Instance

View File

@ -14,22 +14,22 @@ GitLab is creating AI-assisted features across our DevSecOps platform. These fea
| Goal | Feature | Tier/Offering/Status |
|---|---|---|
| Helps you discover or recall Git commands when and where you need them. | [Git suggestions](https://gitlab.com/gitlab-org/gitlab/-/issues/409636) | **(ULTIMATE SAAS EXPERIMENT)** |
| Assists with quickly getting everyone up to speed on lengthy conversations to help ensure you are all on the same page. | [Discussion summary](#summarize-issue-discussions-with-discussion-summary) | **(ULTIMATE SAAS EXPERIMENT)** |
| Generates issue descriptions. | [Issue description generation](#summarize-an-issue-with-issue-description-generation) | **(ULTIMATE SAAS EXPERIMENT)** |
| Helps you write code more efficiently by viewing code suggestions as you type. <br><br><i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=hCAyCTacdAQ) | [Code Suggestions](project/repository/code_suggestions/index.md) | For SaaS: **(FREE)**<br><br> For self-managed: **(PREMIUM)** |
| Automates repetitive tasks and helps catch bugs early. | [Test generation](gitlab_duo_chat.md#write-tests-in-the-ide) | **(ULTIMATE BETA)** |
| Generates a description for the merge request based on the contents of the template. | [Merge request template population](project/merge_requests/ai_in_merge_requests.md#fill-in-merge-request-templates) | **(ULTIMATE SAAS EXPERIMENT)** |
| Assists in creating faster and higher-quality reviews by automatically suggesting reviewers for your merge request. <br><br><i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=ivwZQgh4Rxw) | [Suggested Reviewers](project/merge_requests/reviews/index.md#gitlab-duo-suggested-reviewers) | **(ULTIMATE SAAS)** |
| Efficiently communicates the impact of your merge request changes. | [Merge request summary](project/merge_requests/ai_in_merge_requests.md#summarize-merge-request-changes) | **(ULTIMATE SAAS EXPERIMENT)** |
| Helps ease merge request handoff between authors and reviewers and help reviewers efficiently understand suggestions. | [Code review summary](project/merge_requests/ai_in_merge_requests.md#summarize-my-merge-request-review) | **(ULTIMATE SAAS EXPERIMENT)** |
| 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 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)** |
| Helps you discover or recall Git commands when and where you need them. | [Git suggestions](https://gitlab.com/gitlab-org/gitlab/-/issues/409636) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** Experiment |
| Assists with quickly getting everyone up to speed on lengthy conversations to help ensure you are all on the same page. | [Discussion summary](#summarize-issue-discussions-with-discussion-summary) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** Experiment |
| Generates issue descriptions. | [Issue description generation](#summarize-an-issue-with-issue-description-generation) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** Experiment |
| Helps you write code more efficiently by viewing code suggestions as you type. <br><br><i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=hCAyCTacdAQ) | [Code Suggestions](project/repository/code_suggestions/index.md) | **Offering:** SaaS **Tier:** Free <br><br>**Offering:** Self-managed **Tier:** Premium and Ultimate |
| Automates repetitive tasks and helps catch bugs early. | [Test generation](gitlab_duo_chat.md#write-tests-in-the-ide) | **Tier:** Ultimate <br>**Offering:** SaaS, self-managed <br>**Status:** Beta |
| Generates a description for the merge request based on the contents of the template. | [Merge request template population](project/merge_requests/ai_in_merge_requests.md#fill-in-merge-request-templates) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** Experiment |
| Assists in creating faster and higher-quality reviews by automatically suggesting reviewers for your merge request. <br><br><i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=ivwZQgh4Rxw) | [Suggested Reviewers](project/merge_requests/reviews/index.md#gitlab-duo-suggested-reviewers) | **Tier:** Ultimate <br>**Offering:** SaaS |
| Efficiently communicates the impact of your merge request changes. | [Merge request summary](project/merge_requests/ai_in_merge_requests.md#summarize-merge-request-changes) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** Experiment |
| Helps ease merge request handoff between authors and reviewers and help reviewers efficiently understand suggestions. | [Code review summary](project/merge_requests/ai_in_merge_requests.md#summarize-my-merge-request-review) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** Experiment |
| 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) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** Beta |
| Generates a merge request containing the changes required to mitigate a vulnerability. | [Vulnerability resolution](application_security/vulnerabilities/index.md#explaining-a-vulnerability) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** 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) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** 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) | **Tier:** Ultimate <br>**Offering:** SaaS, self-managed<br>**Status:** Beta |
| Assists you in determining the root cause for a pipeline failure and failed CI/CD build. | [Root cause analysis](#root-cause-analysis) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** 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) | **Tier:** Ultimate <br>**Offering:** SaaS, self-managed <br>**Status:** Experiment |
| Processes and responds to your questions about your application's usage data. | [Product Analytics](product_analytics/index.md) | **Tier:** Ultimate <br>**Offering:** SaaS <br>**Status:** Experiment |
## Enable AI/ML features

View File

@ -22,7 +22,12 @@ module BulkImports
wiki = context.portable.wiki
url = data[:url].sub("://", "://oauth2:#{context.configuration.access_token}@")
Gitlab::UrlBlocker.validate!(url, schemes: %w[http https], allow_local_network: allow_local_requests?, allow_localhost: allow_local_requests?)
Gitlab::HTTP_V2::UrlBlocker.validate!(
url,
schemes: %w[http https],
allow_local_network: allow_local_requests?,
allow_localhost: allow_local_requests?,
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?)
wiki.create_wiki_repository
wiki.repository.fetch_as_mirror(url)

View File

@ -21,7 +21,12 @@ module BulkImports
url = url.sub("://", "://oauth2:#{context.configuration.access_token}@")
project = context.portable
Gitlab::UrlBlocker.validate!(url, schemes: %w[http https], allow_local_network: allow_local_requests?, allow_localhost: allow_local_requests?)
Gitlab::HTTP_V2::UrlBlocker.validate!(
url,
schemes: %w[http https],
allow_local_network: allow_local_requests?,
allow_localhost: allow_local_requests?,
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?)
project.ensure_repository
project.repository.fetch_as_mirror(url)

View File

@ -53,10 +53,11 @@ module BulkImports
end
def validate_url(url)
Gitlab::UrlBlocker.validate!(
Gitlab::HTTP_V2::UrlBlocker.validate!(
url,
allow_local_network: allow_local_requests?,
allow_localhost: allow_local_requests?,
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?,
schemes: %w[http https]
)
end

View File

@ -33,7 +33,7 @@ module Gitlab
MAX_TEXT_SIZE_LIMIT = 1_000_000
# Migrations before this version may have been removed
MIN_SCHEMA_GITLAB_VERSION = '15.11'
MIN_SCHEMA_GITLAB_VERSION = '16.7'
# Schema we store dynamically managed partitions in (e.g. for time partitioning)
DYNAMIC_PARTITIONS_SCHEMA = :gitlab_partitions_dynamic

View File

@ -232,7 +232,11 @@ module Gitlab
url = Gitlab::CurrentSettings.current_application_settings.error_tracking_api_url ||
'http://localhost:8080'
Gitlab::UrlBlocker.validate!(url, schemes: %w[http https], allow_localhost: true)
Gitlab::HTTP_V2::UrlBlocker.validate!(
url,
schemes: %w[http https],
allow_localhost: true,
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?)
URI(url)
end

View File

@ -58,11 +58,13 @@ module Gitlab
end
def get_resolved_address
validated_pull_url, host = Gitlab::UrlBlocker.validate!(gist.git_pull_url,
schemes: Project::VALID_IMPORT_PROTOCOLS,
ports: Project::VALID_IMPORT_PORTS,
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?)
validated_pull_url, host = Gitlab::HTTP_V2::UrlBlocker.validate!(
gist.git_pull_url,
schemes: Project::VALID_IMPORT_PROTOCOLS,
ports: Project::VALID_IMPORT_PORTS,
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?,
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?)
host.present? ? validated_pull_url.host.to_s : ''
end

View File

@ -52,8 +52,6 @@ module Gitlab
end
def has_attachments?(object)
return true if Feature.disabled?(:github_importer_attachments, project, type: :gitlab_com_derisk)
object_representation(object).has_attachments?
end
end

View File

@ -161,7 +161,11 @@ module Gitlab
def validate_url!
return if Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false, schemes: %w[http https])
Gitlab::HTTP_V2::UrlBlocker.validate!(
api_prefix,
allow_local_network: false,
schemes: %w[http https],
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?)
end
def service_account_exists?(resource)

View File

@ -8,11 +8,12 @@ module Gitlab
end
def call(env)
Gitlab::UrlBlocker.validate!(env[:url],
Gitlab::HTTP_V2::UrlBlocker.validate!(env[:url],
schemes: %w[http https],
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?,
dns_rebind_protection: dns_rebind_protection?
dns_rebind_protection: dns_rebind_protection?,
deny_all_requests_except_allowed: Gitlab::CurrentSettings.deny_all_requests_except_allowed?
)
@app.call(env)

View File

@ -4,7 +4,7 @@ module ServicePing
module ServicePingSettings
extend self
def product_intelligence_enabled?
def enabled_and_consented?
enabled? && !User.single_user&.requires_usage_stats_consent?
end

View File

@ -8,6 +8,7 @@ import Vuex from 'vuex';
import getMRCodequalityAndSecurityReports from '~/diffs/components/graphql/get_mr_codequality_and_security_reports.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { mockTracking } from 'helpers/tracking_helper';
import { TEST_HOST } from 'spec/test_constants';
import App from '~/diffs/components/app.vue';
@ -22,6 +23,7 @@ import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vu
import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
import eventHub from '~/diffs/event_hub';
import notesEventHub from '~/notes/event_hub';
import { EVT_DISCUSSIONS_ASSIGNED } from '~/diffs/constants';
import axios from '~/lib/utils/axios_utils';
@ -888,4 +890,38 @@ describe('diffs/components/app', () => {
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
describe('draft comments', () => {
let trackingSpy;
beforeEach(() => {
trackingSpy = mockTracking(undefined, window.document, jest.spyOn);
});
describe('when adding a new comment to an existing review', () => {
it('sends the correct tracking event', () => {
createComponent({ shouldShow: true });
notesEventHub.$emit('noteFormAddToReview', { name: 'noteFormAddToReview' });
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'merge_request_click_add_to_review_on_changes_tab',
expect.any(Object),
);
});
});
describe('when adding a comment to a new review', () => {
it('sends the correct tracking event', () => {
createComponent({ shouldShow: true });
notesEventHub.$emit('noteFormStartReview', { name: 'noteFormStartReview' });
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'merge_request_click_start_review_on_changes_tab',
expect.any(Object),
);
});
});
});
});

View File

@ -785,11 +785,16 @@ describe('date addition/subtraction methods', () => {
);
});
// NOTE: 2024-02-29 is a leap day
describe('nYearsAfter', () => {
it.each`
date | numberOfYears | expected
${'2020-07-06'} | ${1} | ${'2021-07-06'}
${'2020-07-06'} | ${15} | ${'2035-07-06'}
${'2024-03-02'} | ${1} | ${'2025-03-02'}
${'2024-03-01'} | ${1} | ${'2025-03-01'}
${'2024-02-29'} | ${1} | ${'2025-02-28'}
${'2024-02-28'} | ${1} | ${'2025-02-28'}
`(
'returns $expected for "$numberOfYears year(s) after $date"',
({ date, numberOfYears, expected }) => {
@ -805,6 +810,10 @@ describe('date addition/subtraction methods', () => {
date | numberOfYears | expected
${'2020-07-06'} | ${4} | ${'2016-07-06'}
${'2020-07-06'} | ${1} | ${'2019-07-06'}
${'2024-03-02'} | ${1} | ${'2023-03-02'}
${'2024-03-01'} | ${1} | ${'2023-03-01'}
${'2024-02-29'} | ${1} | ${'2023-02-28'}
${'2024-02-28'} | ${1} | ${'2023-02-28'}
`(
'returns $expected for "$numberOfYears year(s) before $date"',
({ date, numberOfYears, expected }) => {

View File

@ -401,11 +401,28 @@ describe('issue_comment_form component', () => {
let store;
beforeEach(() => {
store = createStore();
store = createStore({
actions: {
saveNote: jest.fn().mockResolvedValue(),
},
});
store.registerModule('batchComments', batchComments());
store.state.batchComments.drafts = [{ note: 'A' }];
});
it('sends the event to indicate that a new draft comment has been added', () => {
const note = 'some note text which enables actually adding a draft note';
jest.spyOn(eventHub, '$emit');
mountComponent({ mountFunction: mount, initialData: { note }, store });
findAddToReviewButton().trigger('click');
expect(eventHub.$emit).toHaveBeenCalledWith('noteFormAddToReview', {
name: 'noteFormAddToReview',
});
});
it('should save note draft when cmd+enter is pressed', async () => {
mountComponent({ mountFunction: mount, store });
jest.spyOn(wrapper.vm, 'handleSaveDraft');

View File

@ -7,6 +7,7 @@ import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import CommentFieldLayout from '~/notes/components/comment_field_layout.vue';
import { AT_WHO_ACTIVE_CLASS } from '~/gfm_auto_complete';
import eventHub from '~/environments/event_hub';
import notesEventHub from '~/notes/event_hub';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking } from 'helpers/tracking_helper';
import { noteableDataMock, notesDataMock, discussionMock, note } from '../mock_data';
@ -16,6 +17,7 @@ jest.mock('~/lib/utils/autosave');
describe('issue_note_form component', () => {
let store;
let wrapper;
let textarea;
let props;
let trackingSpy;
@ -39,10 +41,13 @@ describe('issue_note_form component', () => {
},
},
});
textarea = wrapper.find('textarea');
};
const findCancelButton = () => wrapper.findByTestId('cancel');
const findCancelCommentButton = () => wrapper.findByTestId('cancelBatchCommentsEnabled');
const findAddToStartReviewButton = () => wrapper.findByTestId('start-review-button');
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
beforeEach(() => {
@ -112,14 +117,10 @@ describe('issue_note_form component', () => {
});
it('should render text area with placeholder', () => {
const textarea = wrapper.find('textarea');
expect(textarea.attributes('placeholder')).toBe('Write a comment or drag your files here…');
});
it('should set data-supports-quick-actions to enable autocomplete', () => {
const textarea = wrapper.find('textarea');
expect(textarea.attributes('data-supports-quick-actions')).toBe('true');
});
@ -147,10 +148,7 @@ describe('issue_note_form component', () => {
});
describe('keyboard events', () => {
let textarea;
beforeEach(() => {
textarea = wrapper.find('textarea');
textarea.setValue('Foo');
});
@ -213,7 +211,6 @@ describe('issue_note_form component', () => {
it('should be possible to update the note', () => {
createComponentWrapper();
const textarea = wrapper.find('textarea');
textarea.setValue('Foo');
const saveButton = wrapper.find('.js-vue-issue-save');
saveButton.vm.$emit('click');
@ -224,7 +221,6 @@ describe('issue_note_form component', () => {
it('tracks event when save button is clicked', () => {
createComponentWrapper();
const textarea = wrapper.find('textarea');
textarea.setValue('Foo');
const saveButton = wrapper.find('.js-vue-issue-save');
saveButton.vm.$emit('click');
@ -313,8 +309,6 @@ describe('issue_note_form component', () => {
describe('on enter', () => {
it('should start review or add to review when cmd+enter is pressed', async () => {
const textarea = wrapper.find('textarea');
textarea.setValue('Foo');
textarea.trigger('keydown.enter', { metaKey: true });
@ -325,5 +319,40 @@ describe('issue_note_form component', () => {
]);
});
});
describe('when adding a draft comment', () => {
beforeEach(() => {
jest.spyOn(notesEventHub, '$emit');
});
it('sends the event to indicate that a draft has been added to the review', () => {
store.state.batchComments.drafts = [{ note: 'A' }];
createComponentWrapper({
isDraft: true,
noteId: '',
discussion: { ...discussionMock, for_commit: false },
});
findAddToStartReviewButton().trigger('click');
expect(notesEventHub.$emit).toHaveBeenCalledWith('noteFormAddToReview', {
name: 'noteFormAddToReview',
});
});
it('sends the event to indicate that a review has been started with the new draft', () => {
createComponentWrapper({
isDraft: true,
noteId: '',
discussion: { ...discussionMock, for_commit: false },
});
findAddToStartReviewButton().trigger('click');
expect(notesEventHub.$emit).toHaveBeenCalledWith('noteFormStartReview', {
name: 'noteFormStartReview',
});
});
});
});
});

View File

@ -3,6 +3,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { nextTick } from 'vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import { mockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import DraftNote from '~/batch_comments/components/draft_note.vue';
import batchComments from '~/batch_comments/stores/modules/batch_comments';
@ -10,6 +11,7 @@ import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { getLocationHash } from '~/lib/utils/url_utility';
import * as urlUtility from '~/lib/utils/url_utility';
import notesEventHub from '~/notes/event_hub';
import CommentForm from '~/notes/components/comment_form.vue';
import NotesApp from '~/notes/components/notes_app.vue';
import NotesActivityHeader from '~/notes/components/notes_activity_header.vue';
@ -453,4 +455,39 @@ describe('note_app', () => {
});
});
});
describe('draft comments', () => {
let trackingSpy;
beforeEach(() => {
window.mrTabs = { eventHub: notesEventHub };
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
trackingSpy = mockTracking(undefined, window.document, jest.spyOn);
wrapper = mountComponent();
});
describe('when adding a new comment to an existing review', () => {
it('sends the correct tracking event', () => {
notesEventHub.$emit('noteFormAddToReview', { name: 'noteFormAddToReview' });
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'merge_request_click_add_to_review_on_overview_tab',
expect.any(Object),
);
});
});
describe('when adding a comment to a new review', () => {
it('sends the correct tracking event', () => {
notesEventHub.$emit('noteFormStartReview', { name: 'noteFormStartReview' });
expect(trackingSpy).toHaveBeenCalledWith(
undefined,
'merge_request_click_start_review_on_overview_tab',
expect.any(Object),
);
});
});
});
});

View File

@ -0,0 +1,49 @@
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import StorageTypeHelpLink from '~/usage_quotas/storage/components/storage_type_help_link.vue';
import { storageTypeHelpPaths } from '~/usage_quotas/storage/constants';
let wrapper;
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(StorageTypeHelpLink, {
propsData: {
helpLinks: storageTypeHelpPaths,
...props,
},
});
};
const findLink = () => wrapper.findComponent(GlLink);
describe('StorageTypeHelpLink', () => {
describe('Storage type w/ link', () => {
describe.each(Object.entries(storageTypeHelpPaths))('%s', (storageType, url) => {
beforeEach(() => {
createComponent({
props: {
storageType,
},
});
});
it('will have proper href', () => {
expect(findLink().attributes('href')).toBe(url);
});
});
});
describe('Storage type w/o help link', () => {
beforeEach(() => {
createComponent({
props: {
storageType: 'Yellow Submarine',
},
});
});
it('will not have a href', () => {
expect(findLink().attributes('href')).toBe(undefined);
});
});
});

View File

@ -1,6 +1,7 @@
import mockGetProjectStorageStatisticsGraphQLResponse from 'test_fixtures/graphql/usage_quotas/storage/project_storage.query.graphql.json';
import mockGetNamespaceStorageGraphQLResponse from 'test_fixtures/graphql/usage_quotas/storage/namespace_storage.query.graphql.json';
import mockGetProjectListStorageGraphQLResponse from 'test_fixtures/graphql/usage_quotas/storage/project_list_storage.query.graphql.json';
import { storageTypeHelpPaths } from '~/usage_quotas/storage/constants';
export { mockGetProjectStorageStatisticsGraphQLResponse };
export { mockGetNamespaceStorageGraphQLResponse };
@ -15,4 +16,6 @@ export const defaultProjectProvideValues = {
export const defaultNamespaceProvideValues = {
userNamespace: false,
namespaceId: '42',
defaultPerPage: 20,
helpLinks: storageTypeHelpPaths,
};

View File

@ -167,13 +167,16 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
before do
allow(::Gitlab::CurrentSettings)
.to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true)
allow(::Gitlab::CurrentSettings)
.to receive(:deny_all_requests_except_allowed?).and_return(true)
end
it 'raises error' do
expect(Gitlab::UrlBlocker)
expect(Gitlab::HTTP_V2::UrlBlocker)
.to receive(:validate!)
.with(url, ports: [80, 443], schemes: %w[http https git],
allow_localhost: true, allow_local_network: true)
allow_localhost: true, allow_local_network: true,
deny_all_requests_except_allowed: true)
.and_raise(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
expect { subject.execute }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
@ -184,13 +187,16 @@ RSpec.describe Gitlab::GithubGistsImport::Importer::GistImporter, feature_catego
before do
allow(::Gitlab::CurrentSettings)
.to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
allow(::Gitlab::CurrentSettings)
.to receive(:deny_all_requests_except_allowed?).and_return(true)
end
it 'raises error' do
expect(Gitlab::UrlBlocker)
expect(Gitlab::HTTP_V2::UrlBlocker)
.to receive(:validate!)
.with(url, ports: [80, 443], schemes: %w[http https git],
allow_localhost: false, allow_local_network: false)
allow_localhost: false, allow_local_network: false,
deny_all_requests_except_allowed: true)
.and_raise(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)
expect { subject.execute }.to raise_error(Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError)

View File

@ -41,20 +41,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::IssuesImporter, feat
importer.sequential_import
end
context 'when flag is disabled' do
before do
stub_feature_flags(github_importer_attachments: false)
end
it 'executes importer for both issues' do
expect_next_instances_of(Gitlab::GithubImport::Importer::NoteAttachmentsImporter, 2) do |importer|
expect(importer).to receive(:execute)
end
importer.sequential_import
end
end
context 'when issue has already been processed' do
before do
importer.mark_as_imported(issue_with_attachment)

View File

@ -42,20 +42,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::MergeRequestsImporte
importer.sequential_import
end
context 'when flag is disabled' do
before do
stub_feature_flags(github_importer_attachments: false)
end
it 'executes importer for both merge requests' do
expect_next_instances_of(Gitlab::GithubImport::Importer::NoteAttachmentsImporter, 2) do |importer|
expect(importer).to receive(:execute)
end
importer.sequential_import
end
end
context 'when merge request has already been processed' do
before do
importer.mark_as_imported(mr_with_attachment)

View File

@ -52,20 +52,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::NotesImporter, featu
importer.sequential_import
end
context 'when flag is disabled' do
before do
stub_feature_flags(github_importer_attachments: false)
end
it 'executes importer for both user notes' do
expect_next_instances_of(Gitlab::GithubImport::Importer::NoteAttachmentsImporter, 2) do |importer|
expect(importer).to receive(:execute)
end
importer.sequential_import
end
end
context 'when note has already been processed' do
before do
importer.mark_as_imported(note_with_attachment)

View File

@ -41,20 +41,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Attachments::ReleasesImporter, fe
importer.sequential_import
end
context 'when flag is disabled' do
before do
stub_feature_flags(github_importer_attachments: false)
end
it 'executes importer for both releases' do
expect_next_instances_of(Gitlab::GithubImport::Importer::NoteAttachmentsImporter, 2) do |importer|
expect(importer).to receive(:execute)
end
importer.sequential_import
end
end
context 'when release has already been processed' do
before do
importer.mark_as_imported(release_with_attachment)

View File

@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe ServicePing::ServicePingSettings do
using RSpec::Parameterized::TableSyntax
describe '#product_intelligence_enabled?' do
where(:usage_ping_enabled, :requires_usage_stats_consent, :expected_product_intelligence_enabled) do
describe '#enabled_and_consented?' do
where(:usage_ping_enabled, :requires_usage_stats_consent, :expected_enabled_and_consented) do
# Usage ping enabled
true | false | true
true | true | false
@ -23,8 +23,8 @@ RSpec.describe ServicePing::ServicePingSettings do
stub_config_setting(usage_ping_enabled: usage_ping_enabled)
end
it 'has the correct product_intelligence_enabled?' do
expect(described_class.product_intelligence_enabled?).to eq(expected_product_intelligence_enabled)
it 'has the correct enabled_and_consented?' do
expect(described_class.enabled_and_consented?).to eq(expected_enabled_and_consented)
end
end
end

View File

@ -104,20 +104,20 @@ RSpec.describe ServicePing::SubmitService, feature_category: :service_ping do
it_behaves_like 'does not run'
end
context 'when product_intelligence_enabled is false' do
context 'when enabled_and_consented is false' do
before do
allow(ServicePing::ServicePingSettings).to receive(:product_intelligence_enabled?).and_return(false)
allow(ServicePing::ServicePingSettings).to receive(:enabled_and_consented?).and_return(false)
end
it_behaves_like 'does not run'
end
context 'when product_intelligence_enabled is true' do
context 'when enabled_and_consented is true' do
before do
stub_usage_data_connections
stub_database_flavor_check
allow(ServicePing::ServicePingSettings).to receive(:product_intelligence_enabled?).and_return(true)
allow(ServicePing::ServicePingSettings).to receive(:enabled_and_consented?).and_return(true)
end
it 'submits a service ping payload without errors', :aggregate_failures do