Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c9439a09c5
commit
66629d156e
|
@ -13,7 +13,7 @@ import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
|
|||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
import { updateHistory } from '~/lib/utils/url_utility';
|
||||
import { sprintf, __, n__ } from '~/locale';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import { ListType } from '../constants';
|
||||
import eventHub from '../eventhub';
|
||||
|
|
|
@ -3,7 +3,7 @@ import { GlTable, GlButton, GlBadge, GlTooltipDirective } from '@gitlab/ui';
|
|||
import { s__ } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
|
||||
export default {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
|||
import { __, s__, sprintf } from '~/locale';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import CommitComponent from '~/vue_shared/components/commit.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import eventHub from '../event_hub';
|
||||
|
|
|
@ -17,7 +17,7 @@ import createFlash from '~/flash';
|
|||
import { __, sprintf, n__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import TrackEventDirective from '~/vue_shared/directives/track_event';
|
||||
import query from '../queries/details.query.graphql';
|
||||
import {
|
||||
|
|
|
@ -302,6 +302,7 @@ export default {
|
|||
unique: true,
|
||||
defaultAuthors: [],
|
||||
fetchAuthors: this.fetchUsers,
|
||||
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-author`,
|
||||
preloadedAuthors,
|
||||
},
|
||||
{
|
||||
|
@ -313,6 +314,7 @@ export default {
|
|||
unique: !this.hasMultipleIssueAssigneesFeature,
|
||||
defaultAuthors: DEFAULT_NONE_ANY,
|
||||
fetchAuthors: this.fetchUsers,
|
||||
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-assignee`,
|
||||
preloadedAuthors,
|
||||
},
|
||||
{
|
||||
|
@ -321,6 +323,7 @@ export default {
|
|||
icon: 'clock',
|
||||
token: MilestoneToken,
|
||||
fetchMilestones: this.fetchMilestones,
|
||||
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-milestone`,
|
||||
},
|
||||
{
|
||||
type: TOKEN_TYPE_LABEL,
|
||||
|
@ -329,6 +332,7 @@ export default {
|
|||
token: LabelToken,
|
||||
defaultLabels: DEFAULT_NONE_ANY,
|
||||
fetchLabels: this.fetchLabels,
|
||||
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-label`,
|
||||
},
|
||||
{
|
||||
type: TOKEN_TYPE_TYPE,
|
||||
|
@ -350,6 +354,7 @@ export default {
|
|||
icon: 'rocket',
|
||||
token: ReleaseToken,
|
||||
fetchReleases: this.fetchReleases,
|
||||
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-release`,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -361,6 +366,7 @@ export default {
|
|||
token: EmojiToken,
|
||||
unique: true,
|
||||
fetchEmojis: this.fetchEmojis,
|
||||
recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-my_reaction`,
|
||||
});
|
||||
|
||||
tokens.push({
|
||||
|
@ -446,7 +452,12 @@ export default {
|
|||
query: searchLabelsQuery,
|
||||
variables: { fullPath: this.fullPath, search, isProject: this.isProject },
|
||||
})
|
||||
.then(({ data }) => data[this.namespace]?.labels.nodes);
|
||||
.then(({ data }) => data[this.namespace]?.labels.nodes)
|
||||
.then((labels) =>
|
||||
// TODO remove once we can search by title-only on the backend
|
||||
// https://gitlab.com/gitlab-org/gitlab/-/issues/346353
|
||||
labels.filter((label) => label.title.toLowerCase().includes(search.toLowerCase())),
|
||||
);
|
||||
},
|
||||
fetchMilestones(search) {
|
||||
return this.$apollo
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { GlButton, GlIcon } from '@gitlab/ui';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import { JOB_SIDEBAR } from '../constants';
|
||||
import ArtifactsBlock from './artifacts_block.vue';
|
||||
import CommitBlock from './commit_block.vue';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { GlIcon, GlLink, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.graphql';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import {
|
||||
EDITOR_APP_STATUS_EMPTY,
|
||||
EDITOR_APP_STATUS_LOADING,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { capitalize, escape } from 'lodash';
|
||||
import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlTabs, GlTab } from '@gitlab/ui';
|
||||
import API from '~/api';
|
||||
import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility';
|
||||
import PipelineCharts from './pipeline_charts.vue';
|
||||
|
||||
|
@ -13,6 +14,9 @@ export default {
|
|||
LeadTimeCharts: () => import('ee_component/dora/components/lead_time_charts.vue'),
|
||||
ProjectQualitySummary: () => import('ee_component/project_quality_summary/app.vue'),
|
||||
},
|
||||
piplelinesTabEvent: 'p_analytics_ci_cd_pipelines',
|
||||
deploymentFrequencyTabEvent: 'p_analytics_ci_cd_deployment_frequency',
|
||||
leadTimeTabEvent: 'p_analytics_ci_cd_lead_time',
|
||||
inject: {
|
||||
shouldRenderDoraCharts: {
|
||||
type: Boolean,
|
||||
|
@ -60,20 +64,35 @@ export default {
|
|||
updateHistory({ url: path, title: window.title });
|
||||
}
|
||||
},
|
||||
trackTabClick(tab) {
|
||||
API.trackRedisHllUserEvent(tab);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<gl-tabs v-if="charts.length > 1" :value="selectedTab" @input="onTabChange">
|
||||
<gl-tab :title="__('Pipelines')">
|
||||
<gl-tab
|
||||
:title="__('Pipelines')"
|
||||
data-testid="pipelines-tab"
|
||||
@click="trackTabClick($options.piplelinesTabEvent)"
|
||||
>
|
||||
<pipeline-charts />
|
||||
</gl-tab>
|
||||
<template v-if="shouldRenderDoraCharts">
|
||||
<gl-tab :title="__('Deployment frequency')">
|
||||
<gl-tab
|
||||
:title="__('Deployment frequency')"
|
||||
data-testid="deployment-frequency-tab"
|
||||
@click="trackTabClick($options.deploymentFrequencyTabEvent)"
|
||||
>
|
||||
<deployment-frequency-charts />
|
||||
</gl-tab>
|
||||
<gl-tab :title="__('Lead time')">
|
||||
<gl-tab
|
||||
:title="__('Lead time')"
|
||||
data-testid="lead-time-tab"
|
||||
@click="trackTabClick($options.leadTimeTabEvent)"
|
||||
>
|
||||
<lead-time-charts />
|
||||
</gl-tab>
|
||||
</template>
|
||||
|
|
|
@ -12,6 +12,8 @@ export const loadViewer = (type) => {
|
|||
return () => import(/* webpackChunkName: 'blob_image_viewer' */ './image_viewer.vue');
|
||||
case 'video':
|
||||
return () => import(/* webpackChunkName: 'blob_video_viewer' */ './video_viewer.vue');
|
||||
case 'pdf':
|
||||
return () => import(/* webpackChunkName: 'blob_pdf_viewer' */ './pdf_viewer.vue');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -36,5 +38,8 @@ export const viewerProps = (type, blob) => {
|
|||
video: {
|
||||
url: blob.rawPath,
|
||||
},
|
||||
pdf: {
|
||||
url: blob.rawPath,
|
||||
},
|
||||
}[type];
|
||||
};
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<script>
|
||||
import PdfViewer from '~/blob/pdf/pdf_viewer.vue';
|
||||
|
||||
export default {
|
||||
components: { PdfViewer },
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<pdf-viewer :pdf="url" />
|
||||
</template>
|
|
@ -116,7 +116,7 @@ export default {
|
|||
statusTokenConfig,
|
||||
{
|
||||
...tagTokenConfig,
|
||||
recentTokenValuesStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`,
|
||||
recentSuggestionsStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
|
||||
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import RunnerName from '../runner_name.vue';
|
||||
import RunnerTypeBadge from '../runner_type_badge.vue';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { __, s__ } from '~/locale';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
|
|
@ -68,7 +68,6 @@ export default {
|
|||
:config="config"
|
||||
:suggestions-loading="loading"
|
||||
:suggestions="tags"
|
||||
:recent-suggestions-storage-key="config.recentTokenValuesStorageKey"
|
||||
@fetch-suggestions="fetchTags"
|
||||
v-on="$listeners"
|
||||
>
|
||||
|
|
|
@ -266,7 +266,7 @@ export default {
|
|||
</gl-popover>
|
||||
</template>
|
||||
<template #collapsed>
|
||||
<div v-gl-tooltip :title="dateLabel" class="sidebar-collapsed-icon">
|
||||
<div v-gl-tooltip.viewport.left :title="dateLabel" class="sidebar-collapsed-icon">
|
||||
<gl-icon :size="16" name="calendar" />
|
||||
<span class="collapse-truncated-title">{{ formattedDate }}</span>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import {
|
||||
MANUAL_DEPLOY,
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { constructWebIDEPath } from '~/lib/utils/url_utility';
|
||||
import { s__ } from '~/locale';
|
||||
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
||||
import MrWidgetHowToMergeModal from './mr_widget_how_to_merge_modal.vue';
|
||||
import MrWidgetIcon from './mr_widget_icon.vue';
|
||||
|
|
|
@ -15,7 +15,7 @@ import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mi
|
|||
import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import { MT_MERGE_STRATEGY } from '../constants';
|
||||
|
||||
export default {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { GlTooltipDirective, GlLink, GlIcon } from '@gitlab/ui';
|
||||
import { isString, isEmpty } from 'lodash';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import UserAvatarLink from './user_avatar/user_avatar_link.vue';
|
||||
|
||||
export default {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { GlIcon, GlButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitl
|
|||
import { convertToFixedRange, isEqualTimeRanges, findTimeRange } from '~/lib/utils/datetime_range';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import DateTimePickerInput from './date_time_picker_input.vue';
|
||||
import {
|
||||
defaultTimeRanges,
|
||||
|
|
|
@ -87,7 +87,6 @@ export default {
|
|||
:get-active-token-value="getActiveAuthor"
|
||||
:default-suggestions="defaultAuthors"
|
||||
:preloaded-suggestions="preloadedAuthors"
|
||||
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
|
||||
@fetch-suggestions="fetchAuthors"
|
||||
v-on="$listeners"
|
||||
>
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlDropdownSectionHeader,
|
||||
GlDropdownText,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
|
@ -17,6 +18,7 @@ export default {
|
|||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlDropdownSectionHeader,
|
||||
GlDropdownText,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
|
@ -57,11 +59,6 @@ export default {
|
|||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
recentSuggestionsStorageKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
valueIdentifier: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -76,14 +73,14 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
searchKey: '',
|
||||
recentSuggestions: this.recentSuggestionsStorageKey
|
||||
? getRecentlyUsedSuggestions(this.recentSuggestionsStorageKey)
|
||||
recentSuggestions: this.config.recentSuggestionsStorageKey
|
||||
? getRecentlyUsedSuggestions(this.config.recentSuggestionsStorageKey)
|
||||
: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isRecentSuggestionsEnabled() {
|
||||
return Boolean(this.recentSuggestionsStorageKey);
|
||||
return Boolean(this.config.recentSuggestionsStorageKey);
|
||||
},
|
||||
recentTokenIds() {
|
||||
return this.recentSuggestions.map((tokenValue) => tokenValue[this.valueIdentifier]);
|
||||
|
@ -119,6 +116,9 @@ export default {
|
|||
showDefaultSuggestions() {
|
||||
return this.availableDefaultSuggestions.length > 0;
|
||||
},
|
||||
showNoMatchesText() {
|
||||
return this.searchKey && !this.availableSuggestions.length;
|
||||
},
|
||||
showRecentSuggestions() {
|
||||
return (
|
||||
this.isRecentSuggestionsEnabled && this.recentSuggestions.length > 0 && !this.searchKey
|
||||
|
@ -167,7 +167,9 @@ export default {
|
|||
this.$emit('fetch-suggestions', search);
|
||||
}
|
||||
}, DEBOUNCE_DELAY),
|
||||
handleTokenValueSelected(activeTokenValue) {
|
||||
handleTokenValueSelected(selectedValue) {
|
||||
const activeTokenValue = this.getActiveTokenValue(this.suggestions, selectedValue);
|
||||
|
||||
// Make sure that;
|
||||
// 1. Recently used values feature is enabled
|
||||
// 2. User has actually selected a value
|
||||
|
@ -177,7 +179,7 @@ export default {
|
|||
activeTokenValue &&
|
||||
!this.preloadedTokenIds.includes(activeTokenValue[this.valueIdentifier])
|
||||
) {
|
||||
setTokenValueToRecentlyUsed(this.recentSuggestionsStorageKey, activeTokenValue);
|
||||
setTokenValueToRecentlyUsed(this.config.recentSuggestionsStorageKey, activeTokenValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -192,7 +194,7 @@ export default {
|
|||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@input="handleInput"
|
||||
@select="handleTokenValueSelected(activeTokenValue)"
|
||||
@select="handleTokenValueSelected"
|
||||
>
|
||||
<template #view-token="viewTokenProps">
|
||||
<slot name="view-token" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
|
||||
|
@ -222,6 +224,9 @@ export default {
|
|||
:suggestions="preloadedSuggestions"
|
||||
></slot>
|
||||
<gl-loading-icon v-if="suggestionsLoading" size="sm" />
|
||||
<gl-dropdown-text v-else-if="showNoMatchesText">
|
||||
{{ __('No matches found') }}
|
||||
</gl-dropdown-text>
|
||||
<template v-else>
|
||||
<slot name="suggestions-list" :suggestions="availableSuggestions"></slot>
|
||||
</template>
|
||||
|
|
|
@ -104,7 +104,6 @@ export default {
|
|||
:suggestions="labels"
|
||||
:get-active-token-value="getActiveLabel"
|
||||
:default-suggestions="defaultLabels"
|
||||
:recent-suggestions-storage-key="config.recentSuggestionsStorageKey"
|
||||
@fetch-suggestions="fetchLabels"
|
||||
v-on="$listeners"
|
||||
>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
|
||||
export default {
|
||||
name: 'MetadataItem',
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
<script>
|
||||
import { dateInWords, timeFor } from '~/lib/utils/datetime_utility';
|
||||
import { __ } from '~/locale';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
|
||||
|
||||
export default {
|
||||
name: 'SidebarCollapsedGroupedDatePicker',
|
||||
components: {
|
||||
collapsedCalendarIcon,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
minDate: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
maxDate: {
|
||||
type: Date,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
disableClickableIcons: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasMinAndMaxDates() {
|
||||
return this.minDate && this.maxDate;
|
||||
},
|
||||
hasNoMinAndMaxDates() {
|
||||
return !this.minDate && !this.maxDate;
|
||||
},
|
||||
showMinDateBlock() {
|
||||
return this.minDate || this.hasNoMinAndMaxDates;
|
||||
},
|
||||
showFromText() {
|
||||
return !this.maxDate && this.minDate;
|
||||
},
|
||||
iconClass() {
|
||||
const disabledClass = this.disableClickableIcons ? 'disabled' : '';
|
||||
return `sidebar-collapsed-icon calendar-icon ${disabledClass}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleSidebar() {
|
||||
this.$emit('toggleCollapse');
|
||||
},
|
||||
dateText(dateType = 'min') {
|
||||
const date = this[`${dateType}Date`];
|
||||
const dateWords = dateInWords(date, true);
|
||||
const parsedDateWords = dateWords ? dateWords.replace(',', '') : dateWords;
|
||||
|
||||
return date ? parsedDateWords : __('None');
|
||||
},
|
||||
tooltipText(dateType = 'min') {
|
||||
const defaultText = dateType === 'min' ? __('Start date') : __('Due date');
|
||||
const date = this[`${dateType}Date`];
|
||||
const timeAgo = dateType === 'min' ? this.timeFormatted(date) : timeFor(date);
|
||||
const dateText = date ? [this.dateText(dateType), `(${timeAgo})`].join(' ') : '';
|
||||
|
||||
if (date) {
|
||||
return [defaultText, dateText].join('<br />');
|
||||
}
|
||||
return __('Start and due date');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="block sidebar-grouped-item gl-cursor-pointer" role="button" @click="toggleSidebar">
|
||||
<collapsed-calendar-icon
|
||||
v-if="showMinDateBlock"
|
||||
:container-class="iconClass"
|
||||
:tooltip-text="tooltipText('min')"
|
||||
>
|
||||
<span class="sidebar-collapsed-value">
|
||||
<span v-if="showFromText">{{ __('From') }}</span> <span>{{ dateText('min') }}</span>
|
||||
</span>
|
||||
</collapsed-calendar-icon>
|
||||
<div v-if="hasMinAndMaxDates" class="text-center sidebar-collapsed-divider">-</div>
|
||||
<collapsed-calendar-icon
|
||||
v-if="maxDate"
|
||||
:container-class="iconClass"
|
||||
:tooltip-text="tooltipText('max')"
|
||||
>
|
||||
<span class="sidebar-collapsed-value">
|
||||
<span v-if="!minDate">{{ __('Until') }}</span> <span>{{ dateText('max') }}</span>
|
||||
</span>
|
||||
</collapsed-calendar-icon>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,88 @@
|
|||
/* eslint-disable @gitlab/require-i18n-strings */
|
||||
import TooltipOnTruncate from './tooltip_on_truncate.vue';
|
||||
|
||||
const defaultWidth = '250px';
|
||||
|
||||
export default {
|
||||
component: TooltipOnTruncate,
|
||||
title: 'vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue',
|
||||
};
|
||||
|
||||
const createStory = ({ ...options }) => {
|
||||
return (_, { argTypes }) => {
|
||||
const comp = {
|
||||
components: { TooltipOnTruncate },
|
||||
props: Object.keys(argTypes),
|
||||
template: `
|
||||
<div class="gl-bg-blue-50" :style="{ width }">
|
||||
<tooltip-on-truncate :title="title" :placement="placement" class="gl-display-block gl-text-truncate">
|
||||
{{title}}
|
||||
</tooltip-on-truncate>
|
||||
</div>
|
||||
`,
|
||||
...options,
|
||||
};
|
||||
|
||||
return comp;
|
||||
};
|
||||
};
|
||||
|
||||
export const Default = createStory();
|
||||
Default.args = {
|
||||
width: defaultWidth,
|
||||
title: 'Hover on this text to see the content in a tooltip.',
|
||||
};
|
||||
|
||||
export const NoOverflow = createStory();
|
||||
NoOverflow.args = {
|
||||
width: defaultWidth,
|
||||
title: "Short text doesn't need a tooltip.",
|
||||
};
|
||||
|
||||
export const Placement = createStory();
|
||||
Placement.args = {
|
||||
width: defaultWidth,
|
||||
title: 'Use `placement="right"` to display this tooltip at the right.',
|
||||
placement: 'right',
|
||||
};
|
||||
|
||||
const TIMEOUT_S = 3;
|
||||
|
||||
export const LiveUpdates = createStory({
|
||||
props: ['width', 'placement'],
|
||||
data() {
|
||||
return {
|
||||
title: `(loading in ${TIMEOUT_S}s)`,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.title = 'Content updated! The content is now overflowing so we use a tooltip!';
|
||||
}, TIMEOUT_S * 1000);
|
||||
},
|
||||
});
|
||||
LiveUpdates.args = {
|
||||
width: defaultWidth,
|
||||
};
|
||||
LiveUpdates.argTypes = {
|
||||
title: {
|
||||
control: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const TruncateTarget = createStory({
|
||||
template: `
|
||||
<div class="gl-bg-black" :style="{ width }">
|
||||
<tooltip-on-truncate class="gl-display-flex" :truncate-target="truncateTarget" :title="title">
|
||||
<div class="gl-m-5 gl-bg-blue-50 gl-text-truncate">
|
||||
{{ title }}
|
||||
</div>
|
||||
</tooltip-on-truncate>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
TruncateTarget.args = {
|
||||
width: defaultWidth,
|
||||
truncateTarget: 'child',
|
||||
title: 'Wrap in container and use `truncate-target="child"` prop.',
|
||||
};
|
|
@ -31,6 +31,7 @@ class Admin::PlanLimitsController < Admin::ApplicationController
|
|||
params.require(:plan_limits).permit(%i[
|
||||
plan_id
|
||||
conan_max_file_size
|
||||
helm_max_file_size
|
||||
maven_max_file_size
|
||||
npm_max_file_size
|
||||
nuget_max_file_size
|
||||
|
|
|
@ -19,7 +19,7 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
|
|||
feature_category :dependency_proxy
|
||||
|
||||
def manifest
|
||||
result = DependencyProxy::FindOrCreateManifestService.new(group, image, tag, token).execute
|
||||
result = DependencyProxy::FindCachedManifestService.new(group, image, tag, token).execute
|
||||
|
||||
if result[:status] == :success
|
||||
if result[:manifest]
|
||||
|
|
|
@ -19,8 +19,13 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
|
||||
around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
|
||||
|
||||
# Will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/345074
|
||||
track_redis_hll_event :charts, name: 'p_analytics_pipelines'
|
||||
|
||||
track_redis_hll_event :charts, name: 'p_analytics_ci_cd_pipelines', if: -> { should_track_ci_cd_pipelines? }
|
||||
track_redis_hll_event :charts, name: 'p_analytics_ci_cd_deployment_frequency', if: -> { should_track_ci_cd_deployment_frequency? }
|
||||
track_redis_hll_event :charts, name: 'p_analytics_ci_cd_lead_time', if: -> { should_track_ci_cd_lead_time? }
|
||||
|
||||
wrap_parameters Ci::Pipeline
|
||||
|
||||
POLLING_INTERVAL = 10_000
|
||||
|
@ -323,6 +328,18 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
e.record!
|
||||
end
|
||||
end
|
||||
|
||||
def should_track_ci_cd_pipelines?
|
||||
params[:chart].blank? || params[:chart] == 'pipelines'
|
||||
end
|
||||
|
||||
def should_track_ci_cd_deployment_frequency?
|
||||
params[:chart] == 'deployment-frequency'
|
||||
end
|
||||
|
||||
def should_track_ci_cd_lead_time?
|
||||
params[:chart] == 'lead-time'
|
||||
end
|
||||
end
|
||||
|
||||
Projects::PipelinesController.prepend_mod_with('Projects::PipelinesController')
|
||||
|
|
|
@ -26,6 +26,12 @@ module Analytics
|
|||
:project_id
|
||||
end
|
||||
|
||||
def self.distinct_stages_within_hierarchy(group)
|
||||
with_preloaded_labels
|
||||
.where(project_id: group.all_projects.select(:id))
|
||||
.select("DISTINCT ON(stage_event_hash_id) #{quoted_table_name}.*")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Project should belong to a group when the stage has Label based events since only GroupLabels are allowed.
|
||||
|
|
|
@ -957,7 +957,7 @@ module Ci
|
|||
.limit(100)
|
||||
.pluck(:expanded_environment_name)
|
||||
|
||||
Environment.where(project: project, name: expanded_environment_names)
|
||||
Environment.where(project: project, name: expanded_environment_names).with_deployments
|
||||
else
|
||||
environment_ids = self_and_descendants.joins(:deployments).select(:'deployments.environment_id')
|
||||
|
||||
|
|
|
@ -59,9 +59,6 @@ class CommitStatus < Ci::ApplicationRecord
|
|||
scope :with_pipeline, -> { joins(:pipeline) }
|
||||
scope :updated_at_before, ->(date) { where('ci_builds.updated_at < ?', date) }
|
||||
scope :created_at_before, ->(date) { where('ci_builds.created_at < ?', date) }
|
||||
scope :updated_before, ->(lookback:, timeout:) {
|
||||
where('(ci_builds.created_at BETWEEN ? AND ?) AND (ci_builds.updated_at BETWEEN ? AND ?)', lookback, timeout, lookback, timeout)
|
||||
}
|
||||
scope :scheduled_at_before, ->(date) {
|
||||
where('ci_builds.scheduled_at IS NOT NULL AND ci_builds.scheduled_at < ?', date)
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ class Environment < ApplicationRecord
|
|||
|
||||
scope :for_project, -> (project) { where(project_id: project) }
|
||||
scope :for_tier, -> (tier) { where(tier: tier).where.not(tier: nil) }
|
||||
scope :with_deployments, -> { where('EXISTS (?)', Deployment.select(1).where('deployments.environment_id = environments.id')) }
|
||||
scope :with_deployment, -> (sha) { where('EXISTS (?)', Deployment.select(1).where('deployments.environment_id = environments.id').where(sha: sha)) }
|
||||
scope :unfoldered, -> { where(environment_type: nil) }
|
||||
scope :with_rank, -> do
|
||||
|
|
|
@ -62,6 +62,7 @@ class InstanceConfiguration
|
|||
def plan_file_size_limits(plan)
|
||||
{
|
||||
conan: plan.actual_limits[:conan_max_file_size],
|
||||
helm: plan.actual_limits[:helm_max_file_size],
|
||||
maven: plan.actual_limits[:maven_max_file_size],
|
||||
npm: plan.actual_limits[:npm_max_file_size],
|
||||
nuget: plan.actual_limits[:nuget_max_file_size],
|
||||
|
|
|
@ -7,7 +7,6 @@ module Ci
|
|||
|
||||
BUILD_PENDING_OUTDATED_TIMEOUT = 1.day
|
||||
BUILD_PENDING_STUCK_TIMEOUT = 1.hour
|
||||
BUILD_LOOKBACK = 5.days
|
||||
|
||||
def execute
|
||||
Gitlab::AppLogger.info "#{self.class}: Cleaning pending timed-out builds"
|
||||
|
@ -30,11 +29,11 @@ module Ci
|
|||
# because we want to force the query planner to use the
|
||||
# `ci_builds_gitlab_monitor_metrics` index all the time.
|
||||
def pending_builds(timeout)
|
||||
if Feature.enabled?(:ci_new_query_for_pending_stuck_jobs)
|
||||
Ci::Build.pending.created_at_before(timeout).updated_at_before(timeout).order(created_at: :asc, project_id: :asc)
|
||||
else
|
||||
Ci::Build.pending.updated_before(lookback: BUILD_LOOKBACK.ago, timeout: timeout)
|
||||
end
|
||||
Ci::Build
|
||||
.pending
|
||||
.created_at_before(timeout)
|
||||
.updated_at_before(timeout)
|
||||
.order(created_at: :asc, project_id: :asc)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class FindOrCreateManifestService < DependencyProxy::BaseService
|
||||
class FindCachedManifestService < DependencyProxy::BaseService
|
||||
def initialize(group, image, tag, token)
|
||||
@group = group
|
||||
@image = image
|
||||
|
@ -20,36 +20,13 @@ module DependencyProxy
|
|||
|
||||
return respond if cached_manifest_matches?(head_result)
|
||||
|
||||
if Feature.enabled?(:dependency_proxy_manifest_workhorse, @group, default_enabled: :yaml)
|
||||
success(manifest: nil, from_cache: false)
|
||||
else
|
||||
pull_new_manifest
|
||||
respond(from_cache: false)
|
||||
end
|
||||
success(manifest: nil, from_cache: false)
|
||||
rescue Timeout::Error, *Gitlab::HTTP::HTTP_ERRORS
|
||||
respond
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pull_new_manifest
|
||||
DependencyProxy::PullManifestService.new(@image, @tag, @token).execute_with_manifest do |new_manifest|
|
||||
params = {
|
||||
file_name: @file_name,
|
||||
content_type: new_manifest[:content_type],
|
||||
digest: new_manifest[:digest],
|
||||
file: new_manifest[:file],
|
||||
size: new_manifest[:file].size
|
||||
}
|
||||
|
||||
if @manifest
|
||||
@manifest.update!(params)
|
||||
else
|
||||
@manifest = @group.dependency_proxy_manifests.create!(params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def cached_manifest_matches?(head_result)
|
||||
return false if head_result[:status] == :error
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class PullManifestService < DependencyProxy::BaseService
|
||||
def initialize(image, tag, token)
|
||||
@image = image
|
||||
@tag = tag
|
||||
@token = token
|
||||
end
|
||||
|
||||
def execute_with_manifest
|
||||
raise ArgumentError, 'Block must be provided' unless block_given?
|
||||
|
||||
response = Gitlab::HTTP.get(manifest_url, headers: auth_headers.merge(Accept: ::ContainerRegistry::Client::ACCEPTED_TYPES.join(',')))
|
||||
|
||||
if response.success?
|
||||
file = Tempfile.new
|
||||
|
||||
begin
|
||||
file.write(response.body)
|
||||
file.flush
|
||||
|
||||
yield(
|
||||
success(
|
||||
file: file,
|
||||
digest: response.headers[DependencyProxy::Manifest::DIGEST_HEADER],
|
||||
content_type: response.headers['content-type']
|
||||
)
|
||||
)
|
||||
ensure
|
||||
file.close
|
||||
file.unlink
|
||||
end
|
||||
else
|
||||
yield(error(response.body, response.code))
|
||||
end
|
||||
rescue Timeout::Error => exception
|
||||
error(exception.message, 599)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def manifest_url
|
||||
registry.manifest_url(@image, @tag)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,7 +16,7 @@ module LooseForeignKeys
|
|||
def execute
|
||||
result = connection.execute(build_query)
|
||||
|
||||
{ affected_rows: result.cmd_tuples, table: loose_foreign_key_definition.to_table }
|
||||
{ affected_rows: result.cmd_tuples, table: loose_foreign_key_definition.from_table }
|
||||
end
|
||||
|
||||
def async_delete?
|
||||
|
@ -48,15 +48,15 @@ module LooseForeignKeys
|
|||
end
|
||||
|
||||
def arel_table
|
||||
@arel_table ||= Arel::Table.new(loose_foreign_key_definition.to_table)
|
||||
@arel_table ||= Arel::Table.new(loose_foreign_key_definition.from_table)
|
||||
end
|
||||
|
||||
def primary_keys
|
||||
@primary_keys ||= connection.primary_keys(loose_foreign_key_definition.to_table).map { |key| arel_table[key] }
|
||||
@primary_keys ||= connection.primary_keys(loose_foreign_key_definition.from_table).map { |key| arel_table[key] }
|
||||
end
|
||||
|
||||
def quoted_table_name
|
||||
@quoted_table_name ||= Arel.sql(connection.quote_table_name(loose_foreign_key_definition.to_table))
|
||||
@quoted_table_name ||= Arel.sql(connection.quote_table_name(loose_foreign_key_definition.from_table))
|
||||
end
|
||||
|
||||
def delete_query
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
.form-group
|
||||
= f.label :conan_max_file_size, _('Maximum Conan package file size in bytes'), class: 'label-bold'
|
||||
= f.number_field :conan_max_file_size, class: 'form-control gl-form-input'
|
||||
.form-group
|
||||
= f.label :helm_max_file_size, _('Maximum Helm chart file size in bytes'), class: 'label-bold'
|
||||
= f.number_field :helm_max_file_size, class: 'form-control gl-form-input'
|
||||
.form-group
|
||||
= f.label :maven_max_file_size, _('Maximum Maven package file size in bytes'), class: 'label-bold'
|
||||
= f.number_field :maven_max_file_size, class: 'form-control gl-form-input'
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
= hidden_field_tag 'new_parent_group_id'
|
||||
|
||||
%ul
|
||||
- side_effects_link_start = '<a href="https://docs.gitlab.com/ee/user/project/index.html#redirects-when-changing-repository-paths" target="_blank">'.html_safe
|
||||
- side_effects_link_start = '<a href="https://docs.gitlab.com/ee/user/project/repository/index.html#what-happens-when-a-repository-path-changes" target="_blank">'.html_safe
|
||||
- warning_text = s_("GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}.") % { side_effects_link_start: side_effects_link_start, side_effects_link_end: '</a>'.html_safe }
|
||||
%li= warning_text.html_safe
|
||||
%li= s_('GroupSettings|You can only transfer the group to a group you manage.')
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
%td= 'Conan'
|
||||
- package_file_size_limits.each_value do |limits|
|
||||
%td= instance_configuration_human_size_cell(limits[:conan])
|
||||
%tr
|
||||
%td= 'Helm'
|
||||
- package_file_size_limits.each_value do |limits|
|
||||
%td= instance_configuration_human_size_cell(limits[:helm])
|
||||
%tr
|
||||
%td= 'Maven'
|
||||
- package_file_size_limits.each_value do |limits|
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: ci_new_query_for_pending_stuck_jobs
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68880
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339322
|
||||
milestone: '14.3'
|
||||
type: development
|
||||
group: group::pipeline execution
|
||||
default_enabled: false
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: dependency_proxy_manifest_workhorse
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73033
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344216
|
||||
milestone: '14.4'
|
||||
type: development
|
||||
group: group::package
|
||||
default_enabled: true
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334162
|
|||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -13,29 +13,32 @@ data_source: redis_hll
|
|||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- users_viewing_analytics_group_devops_adoption
|
||||
- i_analytics_dev_ops_adoption
|
||||
- i_analytics_dev_ops_score
|
||||
- p_analytics_merge_request
|
||||
- i_analytics_instance_statistics
|
||||
- g_analytics_contribution
|
||||
- g_analytics_insights
|
||||
- g_analytics_issues
|
||||
- g_analytics_productivity
|
||||
- g_analytics_valuestream
|
||||
- p_analytics_pipelines
|
||||
- p_analytics_code_reviews
|
||||
- p_analytics_valuestream
|
||||
- p_analytics_insights
|
||||
- p_analytics_issues
|
||||
- p_analytics_repo
|
||||
- i_analytics_cohorts
|
||||
- users_viewing_analytics_group_devops_adoption
|
||||
- i_analytics_dev_ops_adoption
|
||||
- i_analytics_dev_ops_score
|
||||
- p_analytics_merge_request
|
||||
- i_analytics_instance_statistics
|
||||
- g_analytics_contribution
|
||||
- g_analytics_insights
|
||||
- g_analytics_issues
|
||||
- g_analytics_productivity
|
||||
- g_analytics_valuestream
|
||||
- p_analytics_pipelines
|
||||
- p_analytics_ci_cd_pipelines
|
||||
- p_analytics_ci_cd_deployment_frequency
|
||||
- p_analytics_ci_cd_lead_time
|
||||
- p_analytics_code_reviews
|
||||
- p_analytics_valuestream
|
||||
- p_analytics_insights
|
||||
- p_analytics_issues
|
||||
- p_analytics_repo
|
||||
- i_analytics_cohorts
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
performance_indicator_type: []
|
||||
milestone: "<13.9"
|
||||
milestone: '<13.9'
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.analytics.p_analytics_ci_cd_pipelines_monthly
|
||||
description: Count of unique visits to the project level CI CD Analytics pipelines tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
options:
|
||||
events:
|
||||
- p_analytics_ci_cd_pipelines
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.analytics.p_analytics_ci_cd_deployment_frequency_monthly
|
||||
description: Count of unique visits to the project level CI CD Analytics deployment frequency tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
options:
|
||||
events:
|
||||
- p_analytics_ci_cd_deployment_frequency
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.analytics.p_analytics_ci_cd_lead_time_monthly
|
||||
description: Count of unique visits to the project level CI CD Analytics lead time tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
options:
|
||||
events:
|
||||
- p_analytics_ci_cd_lead_time
|
|
@ -13,29 +13,32 @@ data_source: redis_hll
|
|||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- users_viewing_analytics_group_devops_adoption
|
||||
- i_analytics_dev_ops_adoption
|
||||
- i_analytics_dev_ops_score
|
||||
- p_analytics_merge_request
|
||||
- i_analytics_instance_statistics
|
||||
- g_analytics_contribution
|
||||
- g_analytics_insights
|
||||
- g_analytics_issues
|
||||
- g_analytics_productivity
|
||||
- g_analytics_valuestream
|
||||
- p_analytics_pipelines
|
||||
- p_analytics_code_reviews
|
||||
- p_analytics_valuestream
|
||||
- p_analytics_insights
|
||||
- p_analytics_issues
|
||||
- p_analytics_repo
|
||||
- i_analytics_cohorts
|
||||
- users_viewing_analytics_group_devops_adoption
|
||||
- i_analytics_dev_ops_adoption
|
||||
- i_analytics_dev_ops_score
|
||||
- p_analytics_merge_request
|
||||
- i_analytics_instance_statistics
|
||||
- g_analytics_contribution
|
||||
- g_analytics_insights
|
||||
- g_analytics_issues
|
||||
- g_analytics_productivity
|
||||
- g_analytics_valuestream
|
||||
- p_analytics_pipelines
|
||||
- p_analytics_ci_cd_pipelines
|
||||
- p_analytics_ci_cd_deployment_frequency
|
||||
- p_analytics_ci_cd_lead_time
|
||||
- p_analytics_code_reviews
|
||||
- p_analytics_valuestream
|
||||
- p_analytics_insights
|
||||
- p_analytics_issues
|
||||
- p_analytics_repo
|
||||
- i_analytics_cohorts
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
performance_indicator_type: []
|
||||
milestone: "<13.9"
|
||||
milestone: '<13.9'
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.analytics.p_analytics_ci_cd_pipelines_weekly
|
||||
description: Count of unique visits to the project level CI CD Analytics pipelines tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
options:
|
||||
events:
|
||||
- p_analytics_ci_cd_pipelines
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.analytics.p_analytics_ci_cd_deployment_frequency_weekly
|
||||
description: Count of unique visits to the project level CI CD Analytics deployment frequency tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
options:
|
||||
events:
|
||||
- p_analytics_ci_cd_deployment_frequency
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
key_path: redis_hll_counters.analytics.p_analytics_ci_cd_lead_time_weekly
|
||||
description: Count of unique visits to the project level CI CD Analytics lead time tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
data_category: optional
|
||||
instrumentation_class: RedisHLLMetric
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
options:
|
||||
events:
|
||||
- p_analytics_ci_cd_lead_time
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: analytics_unique_visits.p_analytics_ci_cd_pipelines
|
||||
description: Count of unique visits to the project level CI CD Analytics pipelines tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: all
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: analytics_unique_visits.p_analytics_ci_cd_deployment_frequency
|
||||
description: Count of unique visits to the project level CI CD Analytics deployment frequency tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: all
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: analytics_unique_visits.p_analytics_ci_cd_lead_time
|
||||
description: Count of unique visits to the project level CI CD Analytics lead time tab
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: group::optimize
|
||||
product_category:
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '14.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75187
|
||||
time_frame: all
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -848,16 +848,20 @@ More information can be found in the [Push event activities limit and bulk push
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218017) in GitLab 13.4.
|
||||
|
||||
On GitLab.com, the maximum file size for a package that's uploaded to the [GitLab Package Registry](../user/packages/package_registry/index.md) varies by format:
|
||||
The default maximum file size for a package that's uploaded to the [GitLab Package Registry](../user/packages/package_registry/index.md) varies by format:
|
||||
|
||||
- Conan: 5 GB
|
||||
- Conan: 3 GB
|
||||
- Generic: 5 GB
|
||||
- Maven: 5 GB
|
||||
- npm: 5 GB
|
||||
- NuGet: 5 GB
|
||||
- PyPI: 5 GB
|
||||
- Helm: 5 MB
|
||||
- Maven: 3 GB
|
||||
- npm: 500 MB
|
||||
- NuGet: 500 MB
|
||||
- PyPI: 3 GB
|
||||
- Terraform: 1 GB
|
||||
|
||||
To set this limit for a self-managed installation, run the following in the
|
||||
The [maximum file sizes on GitLab.com](../user/gitlab_com/index.md) might be different.
|
||||
|
||||
To set these limits for a self-managed installation, run the following in the
|
||||
[GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session):
|
||||
|
||||
```ruby
|
||||
|
@ -881,6 +885,9 @@ Plan.default.actual_limits.update!(pypi_max_file_size: 100.megabytes)
|
|||
# For Debian Packages
|
||||
Plan.default.actual_limits.update!(debian_max_file_size: 100.megabytes)
|
||||
|
||||
# For Helm Charts
|
||||
Plan.default.actual_limits.update!(helm_max_file_size: 100.megabytes)
|
||||
|
||||
# For Generic Packages
|
||||
Plan.default.actual_limits.update!(generic_packages_max_file_size: 100.megabytes)
|
||||
```
|
||||
|
|
|
@ -62,28 +62,28 @@ following information:
|
|||
- The data cleanup method (`async_delete` or `async_nullify`)
|
||||
|
||||
The YAML file is located at `lib/gitlab/database/gitlab_loose_foreign_keys.yml`. The file groups
|
||||
foreign key definitions by the name of the parent table. The parent table can have multiple loose
|
||||
foreign key definitions by the name of the child table. The child table can have multiple loose
|
||||
foreign key definitions, therefore we store them as an array.
|
||||
|
||||
Example definition:
|
||||
|
||||
```yaml
|
||||
projects:
|
||||
- to_table: ci_pipelines
|
||||
ci_pipelines:
|
||||
- table: projects
|
||||
column: project_id
|
||||
on_delete: async_delete
|
||||
```
|
||||
|
||||
If the `projects` key is already present in the YAML file, then a new entry can be added
|
||||
If the `ci_pipelines` key is already present in the YAML file, then a new entry can be added
|
||||
to the array:
|
||||
|
||||
```yaml
|
||||
projects:
|
||||
- to_table: ci_pipelines
|
||||
ci_pipelines:
|
||||
- table: projects
|
||||
column: project_id
|
||||
on_delete: async_delete
|
||||
- to_table: another_table
|
||||
column: project_id
|
||||
- table: another_table
|
||||
column: another_id
|
||||
on_delete: :async_nullify
|
||||
```
|
||||
|
||||
|
|
|
@ -103,10 +103,14 @@ You can override this behavior by defining specific variables:
|
|||
| Image Path | `$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG` for branch pipelines. `$CI_REGISTRY_IMAGE` for tag pipelines. | `$CI_APPLICATION_REPOSITORY` |
|
||||
| Image Tag | `$CI_COMMIT_SHA` for branch pipelines. `$CI_COMMIT_TAG` for tag pipelines. | `$CI_APPLICATION_TAG` |
|
||||
|
||||
These variables also affect Auto Build. If you don't want to build and push an image to
|
||||
These variables also affect Auto Build and Auto Container Scanning. If you don't want to build and push an image to
|
||||
`$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG`, consider
|
||||
including only `Jobs/Deploy.gitlab-ci.yml`, or [disabling the `build` jobs](#disable-jobs).
|
||||
|
||||
If you use Auto Container Scanning and set a value for `$CI_APPLICATION_REPOSITORY`, then you should
|
||||
also update `$CS_DEFAULT_BRANCH_IMAGE`. See [Setting the default branch image](../../user/application_security/container_scanning/index.md#setting-the-default-branch-image)
|
||||
for more details.
|
||||
|
||||
Here is an example setup in your `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
|
|
|
@ -37,6 +37,7 @@ module API
|
|||
|
||||
optional :conan_max_file_size, type: Integer, desc: 'Maximum Conan package file size in bytes'
|
||||
optional :generic_packages_max_file_size, type: Integer, desc: 'Maximum generic package file size in bytes'
|
||||
optional :helm_max_file_size, type: Integer, desc: 'Maximum Helm chart file size in bytes'
|
||||
optional :maven_max_file_size, type: Integer, desc: 'Maximum Maven package file size in bytes'
|
||||
optional :npm_max_file_size, type: Integer, desc: 'Maximum NPM package file size in bytes'
|
||||
optional :nuget_max_file_size, type: Integer, desc: 'Maximum NuGet package file size in bytes'
|
||||
|
|
|
@ -52,7 +52,7 @@ module API
|
|||
|
||||
# HTTP status codes to terminate the job on GitLab Runner:
|
||||
# - 403
|
||||
def authenticate_job!(require_running: true)
|
||||
def authenticate_job!(require_running: true, heartbeat_runner: false)
|
||||
job = current_job
|
||||
|
||||
# 404 is not returned here because we want to terminate the job if it's
|
||||
|
@ -70,7 +70,17 @@ module API
|
|||
job_forbidden!(job, 'Job is not running') unless job.running?
|
||||
end
|
||||
|
||||
job.runner&.heartbeat(get_runner_ip)
|
||||
# Only some requests (like updating the job or patching the trace) should trigger
|
||||
# runner heartbeat. Operations like artifacts uploading are executed in context of
|
||||
# the running job and in the job environment, which in many cases will cause the IP
|
||||
# to be updated to not the expected value. And operations like artifacts downloads can
|
||||
# be done even after the job is finished and from totally different runners - while
|
||||
# they would then update the connection status of not the runner that they should.
|
||||
# Runner requests done in context of job authentication should explicitly define when
|
||||
# the heartbeat should be triggered.
|
||||
if heartbeat_runner
|
||||
job.runner&.heartbeat(get_runner_ip)
|
||||
end
|
||||
|
||||
job
|
||||
end
|
||||
|
|
|
@ -176,7 +176,7 @@ module API
|
|||
optional :exit_code, type: Integer, desc: %q(Job's exit code)
|
||||
end
|
||||
put '/:id', feature_category: :continuous_integration do
|
||||
job = authenticate_job!
|
||||
job = authenticate_job!(heartbeat_runner: true)
|
||||
|
||||
Gitlab::Metrics.add_event(:update_build)
|
||||
|
||||
|
@ -203,7 +203,7 @@ module API
|
|||
optional :token, type: String, desc: %q(Job's authentication token)
|
||||
end
|
||||
patch '/:id/trace', feature_category: :continuous_integration do
|
||||
job = authenticate_job!
|
||||
job = authenticate_job!(heartbeat_runner: true)
|
||||
|
||||
error!('400 Missing header Content-Range', 400) unless request.headers.key?('Content-Range')
|
||||
content_range = request.headers['Content-Range']
|
||||
|
|
|
@ -5,6 +5,7 @@ module API
|
|||
class PlanLimit < Grape::Entity
|
||||
expose :conan_max_file_size
|
||||
expose :generic_packages_max_file_size
|
||||
expose :helm_max_file_size
|
||||
expose :maven_max_file_size
|
||||
expose :npm_max_file_size
|
||||
expose :nuget_max_file_size
|
||||
|
|
|
@ -53,6 +53,10 @@ variables:
|
|||
# KUBE_INGRESS_BASE_DOMAIN is the application deployment domain and should be set as a variable at the group or project level.
|
||||
# KUBE_INGRESS_BASE_DOMAIN: domain.example.com
|
||||
|
||||
# Allows Container-Scanning to correctly correlate image names when using Jobs/Build.gitlab-ci.yml
|
||||
CI_APPLICATION_TAG: $CI_COMMIT_SHA
|
||||
CS_DEFAULT_BRANCH_IMAGE: $CI_REGISTRY_IMAGE/$CI_DEFAULT_BRANCH:$CI_APPLICATION_TAG
|
||||
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: testing-password
|
||||
POSTGRES_ENABLED: "true"
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
chat_names:
|
||||
- to_table: ci_pipeline_chat_data
|
||||
ci_pipeline_chat_data:
|
||||
- table: chat_names
|
||||
column: chat_name_id
|
||||
on_delete: async_delete
|
||||
ci_builds:
|
||||
- to_table: dast_site_profiles_builds
|
||||
dast_scanner_profiles_builds:
|
||||
- table: ci_builds
|
||||
column: ci_build_id
|
||||
on_delete: async_delete
|
||||
- to_table: dast_scanner_profiles_builds
|
||||
dast_scanner_profiles_builds:
|
||||
- table: ci_builds
|
||||
column: ci_build_id
|
||||
on_delete: async_delete
|
||||
ci_pipelines:
|
||||
- to_table: dast_profiles_pipelines
|
||||
dast_profiles_pipelines:
|
||||
- table: ci_pipelines
|
||||
column: ci_pipeline_id
|
||||
on_delete: async_delete
|
||||
ci_runners:
|
||||
- to_table: clusters_applications_runners
|
||||
clusters_applications_runners:
|
||||
- table: ci_runners
|
||||
column: runner_id
|
||||
on_delete: async_nullify
|
||||
|
|
|
@ -4,25 +4,25 @@ module Gitlab
|
|||
module Database
|
||||
module LooseForeignKeys
|
||||
def self.definitions_by_table
|
||||
@definitions_by_table ||= definitions.group_by(&:from_table).with_indifferent_access.freeze
|
||||
@definitions_by_table ||= definitions.group_by(&:to_table).with_indifferent_access.freeze
|
||||
end
|
||||
|
||||
def self.definitions
|
||||
@definitions ||= loose_foreign_keys_yaml.flat_map do |parent_table_name, configs|
|
||||
configs.map { |config| build_definition(parent_table_name, config) }
|
||||
@definitions ||= loose_foreign_keys_yaml.flat_map do |child_table_name, configs|
|
||||
configs.map { |config| build_definition(child_table_name, config) }
|
||||
end.freeze
|
||||
end
|
||||
|
||||
def self.build_definition(parent_table_name, config)
|
||||
to_table = config.fetch('to_table')
|
||||
def self.build_definition(child_table_name, config)
|
||||
parent_table_name = config.fetch('table')
|
||||
|
||||
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
|
||||
child_table_name,
|
||||
parent_table_name,
|
||||
to_table,
|
||||
{
|
||||
column: config.fetch('column'),
|
||||
on_delete: config.fetch('on_delete').to_sym,
|
||||
gitlab_schema: GitlabSchema.table_schema(to_table)
|
||||
gitlab_schema: GitlabSchema.table_schema(child_table_name)
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
module Gitlab
|
||||
module Tracking
|
||||
SNOWPLOW_NAMESPACE = 'gl'
|
||||
|
||||
class << self
|
||||
def enabled?
|
||||
snowplow_micro_enabled? || Gitlab::CurrentSettings.snowplow_enabled?
|
||||
snowplow.enabled?
|
||||
end
|
||||
|
||||
def event(category, action, label: nil, property: nil, value: nil, context: [], project: nil, user: nil, namespace: nil, **extra) # rubocop:disable Metrics/ParameterLists
|
||||
|
|
|
@ -8,6 +8,8 @@ module Gitlab
|
|||
class Snowplow < Base
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
SNOWPLOW_NAMESPACE = 'gl'
|
||||
|
||||
override :event
|
||||
def event(category, action, label: nil, property: nil, value: nil, context: nil)
|
||||
return unless enabled?
|
||||
|
@ -19,7 +21,7 @@ module Gitlab
|
|||
def options(group)
|
||||
additional_features = Feature.enabled?(:additional_snowplow_tracking, group, type: :ops)
|
||||
{
|
||||
namespace: Gitlab::Tracking::SNOWPLOW_NAMESPACE,
|
||||
namespace: SNOWPLOW_NAMESPACE,
|
||||
hostname: hostname,
|
||||
cookie_domain: cookie_domain,
|
||||
app_id: app_id,
|
||||
|
@ -28,16 +30,16 @@ module Gitlab
|
|||
}.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
|
||||
end
|
||||
|
||||
def enabled?
|
||||
Gitlab::CurrentSettings.snowplow_enabled?
|
||||
end
|
||||
|
||||
def hostname
|
||||
Gitlab::CurrentSettings.snowplow_collector_hostname
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enabled?
|
||||
Gitlab::Tracking.enabled?
|
||||
end
|
||||
|
||||
def app_id
|
||||
Gitlab::CurrentSettings.snowplow_app_id
|
||||
end
|
||||
|
@ -54,7 +56,7 @@ module Gitlab
|
|||
@tracker ||= SnowplowTracker::Tracker.new(
|
||||
emitter,
|
||||
SnowplowTracker::Subject.new,
|
||||
Gitlab::Tracking::SNOWPLOW_NAMESPACE,
|
||||
SNOWPLOW_NAMESPACE,
|
||||
app_id
|
||||
)
|
||||
end
|
||||
|
|
|
@ -18,6 +18,11 @@ module Gitlab
|
|||
).transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
|
||||
end
|
||||
|
||||
override :enabled?
|
||||
def enabled?
|
||||
true
|
||||
end
|
||||
|
||||
override :hostname
|
||||
def hostname
|
||||
"#{uri.host}:#{uri.port}"
|
||||
|
|
|
@ -66,3 +66,15 @@
|
|||
category: analytics
|
||||
redis_slot: analytics
|
||||
aggregation: weekly
|
||||
- name: p_analytics_ci_cd_pipelines
|
||||
category: analytics
|
||||
redis_slot: analytics
|
||||
aggregation: weekly
|
||||
- name: p_analytics_ci_cd_deployment_frequency
|
||||
category: analytics
|
||||
redis_slot: analytics
|
||||
aggregation: weekly
|
||||
- name: p_analytics_ci_cd_lead_time
|
||||
category: analytics
|
||||
redis_slot: analytics
|
||||
aggregation: weekly
|
||||
|
|
|
@ -5469,6 +5469,9 @@ msgstr ""
|
|||
msgid "Billing|Users occupying seats in"
|
||||
msgstr ""
|
||||
|
||||
msgid "Billing|View pending approvals"
|
||||
msgstr ""
|
||||
|
||||
msgid "Billing|You are about to remove user %{username} from your subscription. If you continue, the user will be removed from the %{namespace} group and all its subgroups and projects. This action can't be undone."
|
||||
msgstr ""
|
||||
|
||||
|
@ -13483,9 +13486,6 @@ msgstr ""
|
|||
msgid "Epics|Enter a title for your epic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|How can I solve this?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|Leave empty to inherit from milestone dates"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13534,9 +13534,6 @@ msgstr ""
|
|||
msgid "Epics|Something went wrong while removing issue from epic."
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|These dates affect how your epics appear in the roadmap. Dates from milestones come from the milestones assigned to issues in the epic. You can also set fixed dates or remove them entirely."
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|This epic and any containing child epics are confidential and should only be visible to team members with at least Reporter access."
|
||||
msgstr ""
|
||||
|
||||
|
@ -14997,15 +14994,6 @@ msgstr ""
|
|||
msgid "Fixed burndown chart"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fixed date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fixed due date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fixed start date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fixed:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21412,6 +21400,9 @@ msgstr ""
|
|||
msgid "Maximum Conan package file size in bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Helm chart file size in bytes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum Maven package file size in bytes"
|
||||
msgstr ""
|
||||
|
||||
|
@ -33059,9 +33050,6 @@ msgstr ""
|
|||
msgid "Start a review"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start and due date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start by choosing a group to start exploring the merge requests in that group. You can then proceed to filter by projects, labels, milestones and authors."
|
||||
msgstr ""
|
||||
|
||||
|
@ -35453,12 +35441,6 @@ msgstr ""
|
|||
msgid "This credential has expired"
|
||||
msgstr ""
|
||||
|
||||
msgid "This date is after the due date, so this epic won't appear in the roadmap."
|
||||
msgstr ""
|
||||
|
||||
msgid "This date is before the start date, so this epic won't appear in the roadmap."
|
||||
msgstr ""
|
||||
|
||||
msgid "This device has already been registered with us."
|
||||
msgstr ""
|
||||
|
||||
|
@ -37250,9 +37232,6 @@ msgstr ""
|
|||
msgid "Unsupported todo type passed. Supported todo types are: %{todo_types}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Until"
|
||||
msgstr ""
|
||||
|
||||
msgid "Until revoked, expired personal access tokens pose a security risk."
|
||||
msgstr ""
|
||||
|
||||
|
@ -39877,6 +39856,11 @@ msgstr ""
|
|||
msgid "You don’t have access to Value Stream Analytics for this group"
|
||||
msgstr ""
|
||||
|
||||
msgid "You have %{pendingMembersCount} pending member that needs approval."
|
||||
msgid_plural "You have %{pendingMembersCount} pending members that need approval."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -368,6 +368,10 @@ module QA
|
|||
parse_body(response)
|
||||
end
|
||||
|
||||
def create_wiki_page(title:, content:)
|
||||
api_post_to(api_wikis_path, title: title, content: content)
|
||||
end
|
||||
|
||||
# Object comparison
|
||||
#
|
||||
# @param [QA::Resource::Project] other
|
||||
|
|
|
@ -170,7 +170,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
let(:pull_response) { { status: :success, manifest: manifest, from_cache: false } }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(DependencyProxy::FindOrCreateManifestService) do |instance|
|
||||
allow_next_instance_of(DependencyProxy::FindCachedManifestService) do |instance|
|
||||
allow(instance).to receive(:execute).and_return(pull_response)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -745,9 +745,28 @@ RSpec.describe Projects::PipelinesController do
|
|||
describe 'GET #charts' do
|
||||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
|
||||
it_behaves_like 'tracking unique visits', :charts do
|
||||
let(:request_params) { { namespace_id: project.namespace, project_id: project, id: pipeline.id } }
|
||||
let(:target_id) { 'p_analytics_pipelines' }
|
||||
[
|
||||
{
|
||||
chart_param: '',
|
||||
event: 'p_analytics_ci_cd_pipelines'
|
||||
},
|
||||
{
|
||||
chart_param: 'pipelines',
|
||||
event: 'p_analytics_ci_cd_pipelines'
|
||||
},
|
||||
{
|
||||
chart_param: 'deployment-frequency',
|
||||
event: 'p_analytics_ci_cd_deployment_frequency'
|
||||
},
|
||||
{
|
||||
chart_param: 'lead-time',
|
||||
event: 'p_analytics_ci_cd_lead_time'
|
||||
}
|
||||
].each do |tab|
|
||||
it_behaves_like 'tracking unique visits', :charts do
|
||||
let(:request_params) { { namespace_id: project.namespace, project_id: project, id: pipeline.id, chart: tab[:chart_param] } }
|
||||
let(:target_id) { ['p_analytics_pipelines', tab[:event]] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ RSpec.describe 'Database schema' do
|
|||
ci_builds: %w[erased_by_id runner_id trigger_request_id user_id],
|
||||
ci_namespace_monthly_usages: %w[namespace_id],
|
||||
ci_pipelines: %w[user_id],
|
||||
ci_pipeline_chat_data: %w[chat_name_id], # it uses the loose foreign key featue
|
||||
ci_runner_projects: %w[runner_id],
|
||||
ci_trigger_requests: %w[commit_id],
|
||||
cluster_providers_aws: %w[security_group_id vpc_id access_key_id],
|
||||
|
@ -102,6 +101,8 @@ RSpec.describe 'Database schema' do
|
|||
let(:indexes) { connection.indexes(table) }
|
||||
let(:columns) { connection.columns(table) }
|
||||
let(:foreign_keys) { connection.foreign_keys(table) }
|
||||
let(:loose_foreign_keys) { Gitlab::Database::LooseForeignKeys.definitions.group_by(&:from_table).fetch(table, []) }
|
||||
let(:all_foreign_keys) { foreign_keys + loose_foreign_keys }
|
||||
# take the first column in case we're using a composite primary key
|
||||
let(:primary_key_column) { Array(connection.primary_key(table)).first }
|
||||
|
||||
|
@ -114,7 +115,7 @@ RSpec.describe 'Database schema' do
|
|||
columns = columns.split(',') if columns.is_a?(String)
|
||||
columns.first.chomp
|
||||
end
|
||||
foreign_keys_columns = foreign_keys.map(&:column)
|
||||
foreign_keys_columns = all_foreign_keys.map(&:column)
|
||||
|
||||
# Add the primary key column to the list of indexed columns because
|
||||
# postgres and mysql both automatically create an index on the primary
|
||||
|
@ -129,7 +130,7 @@ RSpec.describe 'Database schema' do
|
|||
context 'columns ending with _id' do
|
||||
let(:column_names) { columns.map(&:name) }
|
||||
let(:column_names_with_id) { column_names.select { |column_name| column_name.ends_with?('_id') } }
|
||||
let(:foreign_keys_columns) { foreign_keys.map(&:column) }
|
||||
let(:foreign_keys_columns) { all_foreign_keys.map(&:column).uniq } # we can have FK and loose FK present at the same time
|
||||
let(:ignored_columns) { ignored_fk_columns(table) }
|
||||
|
||||
it 'do have the foreign keys' do
|
||||
|
|
|
@ -12,6 +12,7 @@ FactoryBot.define do
|
|||
|
||||
trait :with_package_file_sizes do
|
||||
conan_max_file_size { 100 }
|
||||
helm_max_file_size { 100 }
|
||||
maven_max_file_size { 100 }
|
||||
npm_max_file_size { 100 }
|
||||
nuget_max_file_size { 100 }
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { GlTabs, GlTab } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { merge } from 'lodash';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility';
|
||||
import Component from '~/projects/pipelines/charts/components/app.vue';
|
||||
import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue';
|
||||
import API from '~/api';
|
||||
|
||||
jest.mock('~/lib/utils/url_utility');
|
||||
|
||||
|
@ -17,7 +18,7 @@ describe('ProjectsPipelinesChartsApp', () => {
|
|||
let wrapper;
|
||||
|
||||
function createComponent(mountOptions = {}) {
|
||||
wrapper = shallowMount(
|
||||
wrapper = shallowMountExtended(
|
||||
Component,
|
||||
merge(
|
||||
{},
|
||||
|
@ -118,6 +119,23 @@ describe('ProjectsPipelinesChartsApp', () => {
|
|||
|
||||
expect(updateHistory).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('event tracking', () => {
|
||||
it.each`
|
||||
testId | event
|
||||
${'pipelines-tab'} | ${'p_analytics_ci_cd_pipelines'}
|
||||
${'deployment-frequency-tab'} | ${'p_analytics_ci_cd_deployment_frequency'}
|
||||
${'lead-time-tab'} | ${'p_analytics_ci_cd_lead_time'}
|
||||
`('tracks the $event event when clicked', ({ testId, event }) => {
|
||||
jest.spyOn(API, 'trackRedisHllUserEvent');
|
||||
|
||||
expect(API.trackRedisHllUserEvent).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.findByTestId(testId).vm.$emit('click');
|
||||
|
||||
expect(API.trackRedisHllUserEvent).toHaveBeenCalledWith(event);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provided with a query param', () => {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Component from '~/repository/components/blob_viewers/pdf_viewer.vue';
|
||||
import PdfViewer from '~/blob/pdf/pdf_viewer.vue';
|
||||
|
||||
describe('PDF Viewer', () => {
|
||||
let wrapper;
|
||||
|
||||
const propsData = { url: 'some/pdf_blob.pdf' };
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(Component, { propsData });
|
||||
};
|
||||
|
||||
const findPDFViewer = () => wrapper.findComponent(PdfViewer);
|
||||
|
||||
it('renders a PDF Viewer component', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findPDFViewer().exists()).toBe(true);
|
||||
expect(findPDFViewer().props('pdf')).toBe(propsData.url);
|
||||
});
|
||||
});
|
|
@ -147,7 +147,7 @@ describe('AdminRunnersApp', () => {
|
|||
}),
|
||||
expect.objectContaining({
|
||||
type: PARAM_KEY_TAG,
|
||||
recentTokenValuesStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`,
|
||||
recentSuggestionsStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -41,7 +41,7 @@ const mockTagTokenConfig = {
|
|||
title: 'Tags',
|
||||
type: 'tag',
|
||||
token: TagToken,
|
||||
recentTokenValuesStorageKey: mockStorageKey,
|
||||
recentSuggestionsStorageKey: mockStorageKey,
|
||||
operators: OPERATOR_IS_ONLY,
|
||||
};
|
||||
|
||||
|
|
|
@ -46,13 +46,13 @@ const defaultSlots = {
|
|||
};
|
||||
|
||||
const mockProps = {
|
||||
config: mockLabelToken,
|
||||
config: { ...mockLabelToken, recentSuggestionsStorageKey: mockStorageKey },
|
||||
value: { data: '' },
|
||||
active: false,
|
||||
suggestions: [],
|
||||
suggestionsLoading: false,
|
||||
defaultSuggestions: DEFAULT_NONE_ANY,
|
||||
recentSuggestionsStorageKey: mockStorageKey,
|
||||
getActiveTokenValue: (labels, data) => labels.find((label) => label.title === data),
|
||||
};
|
||||
|
||||
function createComponent({
|
||||
|
@ -152,30 +152,22 @@ describe('BaseToken', () => {
|
|||
|
||||
describe('methods', () => {
|
||||
describe('handleTokenValueSelected', () => {
|
||||
it('calls `setTokenValueToRecentlyUsed` when `recentSuggestionsStorageKey` is defined', () => {
|
||||
const mockTokenValue = {
|
||||
id: 1,
|
||||
title: 'Foo',
|
||||
};
|
||||
const mockTokenValue = mockLabels[0];
|
||||
|
||||
wrapper.vm.handleTokenValueSelected(mockTokenValue);
|
||||
it('calls `setTokenValueToRecentlyUsed` when `recentSuggestionsStorageKey` is defined', () => {
|
||||
wrapper.vm.handleTokenValueSelected(mockTokenValue.title);
|
||||
|
||||
expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue);
|
||||
});
|
||||
|
||||
it('does not add token from preloadedSuggestions', async () => {
|
||||
const mockTokenValue = {
|
||||
id: 1,
|
||||
title: 'Foo',
|
||||
};
|
||||
|
||||
wrapper.setProps({
|
||||
preloadedSuggestions: [mockTokenValue],
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
wrapper.vm.handleTokenValueSelected(mockTokenValue);
|
||||
wrapper.vm.handleTokenValueSelected(mockTokenValue.title);
|
||||
|
||||
expect(setTokenValueToRecentlyUsed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -190,7 +182,7 @@ describe('BaseToken', () => {
|
|||
const glFilteredSearchToken = wrapperWithNoStubs.find(GlFilteredSearchToken);
|
||||
|
||||
expect(glFilteredSearchToken.exists()).toBe(true);
|
||||
expect(glFilteredSearchToken.props('config')).toBe(mockLabelToken);
|
||||
expect(glFilteredSearchToken.props('config')).toEqual(mockProps.config);
|
||||
|
||||
wrapperWithNoStubs.destroy();
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import { GlIcon, GlLink } from '@gitlab/ui';
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import component from '~/vue_shared/components/registry/metadata_item.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
|
||||
describe('Metadata Item', () => {
|
||||
let wrapper;
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import CollapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
|
||||
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
|
||||
|
||||
describe('CollapsedGroupedDatePicker', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
showToggleSidebar: true,
|
||||
};
|
||||
|
||||
const minDate = new Date('07/17/2016');
|
||||
const maxDate = new Date('07/17/2017');
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMount(CollapsedGroupedDatePicker, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findCollapsedCalendarIcon = () => wrapper.findComponent(CollapsedCalendarIcon);
|
||||
const findAllCollapsedCalendarIcons = () => wrapper.findAllComponents(CollapsedCalendarIcon);
|
||||
|
||||
describe('toggleCollapse events', () => {
|
||||
it('should emit when collapsed-calendar-icon is clicked', () => {
|
||||
createComponent();
|
||||
|
||||
findCollapsedCalendarIcon().trigger('click');
|
||||
|
||||
expect(wrapper.emitted('toggleCollapse')[0]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('minDate and maxDate', () => {
|
||||
it('should render both collapsed-calendar-icon', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
minDate,
|
||||
maxDate,
|
||||
},
|
||||
});
|
||||
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.length).toBe(2);
|
||||
expect(icons.at(0).text()).toBe('Jul 17 2016');
|
||||
expect(icons.at(1).text()).toBe('Jul 17 2017');
|
||||
});
|
||||
});
|
||||
|
||||
describe('minDate', () => {
|
||||
it('should render minDate in collapsed-calendar-icon', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
minDate,
|
||||
},
|
||||
});
|
||||
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.length).toBe(1);
|
||||
expect(icons.at(0).text()).toBe('From Jul 17 2016');
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxDate', () => {
|
||||
it('should render maxDate in collapsed-calendar-icon', () => {
|
||||
createComponent({
|
||||
props: {
|
||||
maxDate,
|
||||
},
|
||||
});
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.length).toBe(1);
|
||||
expect(icons.at(0).text()).toBe('Until Jul 17 2017');
|
||||
});
|
||||
});
|
||||
|
||||
describe('no dates', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should render None', () => {
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.length).toBe(1);
|
||||
expect(icons.at(0).text()).toBe('None');
|
||||
});
|
||||
|
||||
it('should have tooltip as `Start and due date`', () => {
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.at(0).props('tooltipText')).toBe('Start and due date');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import { hasHorizontalOverflow } from '~/lib/utils/dom_utils';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
|
||||
const MOCK_TITLE = 'lorem-ipsum-dolar-sit-amit-consectur-adipiscing-elit-sed-do';
|
||||
|
|
|
@ -11,6 +11,7 @@ RSpec.describe API::Entities::PlanLimit do
|
|||
expect(subject).to include(
|
||||
:conan_max_file_size,
|
||||
:generic_packages_max_file_size,
|
||||
:helm_max_file_size,
|
||||
:maven_max_file_size,
|
||||
:npm_max_file_size,
|
||||
:nuget_max_file_size,
|
||||
|
|
|
@ -24,19 +24,19 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
|
|||
Gitlab::Database.schemas_to_base_models.fetch(parent_table_schema)
|
||||
end
|
||||
|
||||
it 'all `from_table` tables are present' do
|
||||
definitions.each do |definition|
|
||||
base_models_for(definition.from_table).each do |model|
|
||||
expect(model.connection).to be_table_exist(definition.from_table)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'all `to_table` tables are present' do
|
||||
definitions.each do |definition|
|
||||
base_models_for(definition.to_table).each do |model|
|
||||
expect(model.connection).to be_table_exist(definition.to_table)
|
||||
expect(model.connection).to be_column_exist(definition.to_table, definition.column)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'all `from_table` tables are present' do
|
||||
definitions.each do |definition|
|
||||
base_models_for(definition.from_table).each do |model|
|
||||
expect(model.connection).to be_table_exist(definition.from_table)
|
||||
expect(model.connection).to be_column_exist(definition.from_table, definition.column)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,7 +29,7 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow do
|
|||
|
||||
expect(SnowplowTracker::Tracker)
|
||||
.to receive(:new)
|
||||
.with(emitter, an_instance_of(SnowplowTracker::Subject), Gitlab::Tracking::SNOWPLOW_NAMESPACE, '_abc123_')
|
||||
.with(emitter, an_instance_of(SnowplowTracker::Subject), described_class::SNOWPLOW_NAMESPACE, '_abc123_')
|
||||
.and_return(tracker)
|
||||
end
|
||||
|
||||
|
|
|
@ -1210,6 +1210,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
'i_analytics_cohorts' => 123,
|
||||
'i_analytics_dev_ops_score' => 123,
|
||||
'i_analytics_instance_statistics' => 123,
|
||||
'p_analytics_ci_cd_deployment_frequency' => 123,
|
||||
'p_analytics_ci_cd_lead_time' => 123,
|
||||
'p_analytics_ci_cd_pipelines' => 123,
|
||||
'p_analytics_merge_request' => 123,
|
||||
'i_analytics_dev_ops_adoption' => 123,
|
||||
'users_viewing_analytics_group_devops_adoption' => 123,
|
||||
|
|
|
@ -29,4 +29,29 @@ RSpec.describe Analytics::CycleAnalytics::ProjectStage do
|
|||
let(:default_params) { { project: project } }
|
||||
end
|
||||
end
|
||||
|
||||
describe '.distinct_stages_within_hierarchy' do
|
||||
let_it_be(:top_level_group) { create(:group) }
|
||||
let_it_be(:sub_group_1) { create(:group, parent: top_level_group) }
|
||||
let_it_be(:sub_group_2) { create(:group, parent: sub_group_1) }
|
||||
|
||||
let_it_be(:project_1) { create(:project, group: sub_group_1) }
|
||||
let_it_be(:project_2) { create(:project, group: sub_group_2) }
|
||||
let_it_be(:project_3) { create(:project, group: top_level_group) }
|
||||
|
||||
let_it_be(:stage1) { create(:cycle_analytics_project_stage, project: project_1, start_event_identifier: :issue_created, end_event_identifier: :issue_deployed_to_production) }
|
||||
let_it_be(:stage2) { create(:cycle_analytics_project_stage, project: project_3, start_event_identifier: :issue_created, end_event_identifier: :issue_deployed_to_production) }
|
||||
|
||||
let_it_be(:stage3) { create(:cycle_analytics_project_stage, project: project_1, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
let_it_be(:stage4) { create(:cycle_analytics_project_stage, project: project_3, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
|
||||
subject(:distinct_start_and_end_event_identifiers) { described_class.distinct_stages_within_hierarchy(top_level_group).to_a.pluck(:start_event_identifier, :end_event_identifier) }
|
||||
|
||||
it 'returns distinct stages by start and end events (using stage_event_hash_id)' do
|
||||
expect(distinct_start_and_end_event_identifiers).to match_array([
|
||||
%w[issue_created issue_deployed_to_production],
|
||||
%w[merge_request_created merge_request_merged]
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3180,6 +3180,20 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when an associated environment does not have deployments' do
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, :created) }
|
||||
let_it_be(:build) { create(:ci_build, :stop_review_app, pipeline: pipeline) }
|
||||
let_it_be(:environment) { create(:environment, project: pipeline.project) }
|
||||
|
||||
before_all do
|
||||
build.metadata.update!(expanded_environment_name: environment.name)
|
||||
end
|
||||
|
||||
it 'does not return environments' do
|
||||
expect(subject).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline is in extended family' do
|
||||
let_it_be(:parent) { create(:ci_pipeline) }
|
||||
let_it_be(:parent_build) { create(:ci_build, :with_deployment, environment: 'staging', pipeline: parent) }
|
||||
|
|
|
@ -97,32 +97,6 @@ RSpec.describe CommitStatus do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.updated_before' do
|
||||
let!(:lookback) { 5.days.ago }
|
||||
let!(:timeout) { 1.day.ago }
|
||||
let!(:before_lookback) { lookback - 1.hour }
|
||||
let!(:after_lookback) { lookback + 1.hour }
|
||||
let!(:before_timeout) { timeout - 1.hour }
|
||||
let!(:after_timeout) { timeout + 1.hour }
|
||||
|
||||
subject { described_class.updated_before(lookback: lookback, timeout: timeout) }
|
||||
|
||||
def create_build_with_set_timestamps(created_at:, updated_at:)
|
||||
travel_to(created_at) { create(:ci_build, created_at: Time.current) }.tap do |build|
|
||||
travel_to(updated_at) { build.update!(status: :failed) }
|
||||
end
|
||||
end
|
||||
|
||||
it 'finds builds updated and created in the window between lookback and timeout' do
|
||||
build_in_lookback_timeout_window = create_build_with_set_timestamps(created_at: after_lookback, updated_at: before_timeout)
|
||||
build_outside_lookback_window = create_build_with_set_timestamps(created_at: before_lookback, updated_at: before_timeout)
|
||||
build_outside_timeout_window = create_build_with_set_timestamps(created_at: after_lookback, updated_at: after_timeout)
|
||||
|
||||
expect(subject).to contain_exactly(build_in_lookback_timeout_window)
|
||||
expect(subject).not_to include(build_outside_lookback_window, build_outside_timeout_window)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.scheduled_at_before' do
|
||||
let!(:never_scheduled) { create(:commit_status) }
|
||||
let!(:stale_scheduled) { create(:commit_status, scheduled_at: 1.day.ago) }
|
||||
|
|
|
@ -144,6 +144,7 @@ RSpec.describe InstanceConfiguration do
|
|||
create(:plan_limits,
|
||||
plan: plan1,
|
||||
conan_max_file_size: 1001,
|
||||
helm_max_file_size: 1008,
|
||||
maven_max_file_size: 1002,
|
||||
npm_max_file_size: 1003,
|
||||
nuget_max_file_size: 1004,
|
||||
|
@ -154,6 +155,7 @@ RSpec.describe InstanceConfiguration do
|
|||
create(:plan_limits,
|
||||
plan: plan2,
|
||||
conan_max_file_size: 1101,
|
||||
helm_max_file_size: 1108,
|
||||
maven_max_file_size: 1102,
|
||||
npm_max_file_size: 1103,
|
||||
nuget_max_file_size: 1104,
|
||||
|
@ -166,8 +168,8 @@ RSpec.describe InstanceConfiguration do
|
|||
it 'returns package file size limits' do
|
||||
file_size_limits = subject.settings[:package_file_size_limits]
|
||||
|
||||
expect(file_size_limits[:Plan1]).to eq({ conan: 1001, maven: 1002, npm: 1003, nuget: 1004, pypi: 1005, terraform_module: 1006, generic: 1007 })
|
||||
expect(file_size_limits[:Plan2]).to eq({ conan: 1101, maven: 1102, npm: 1103, nuget: 1104, pypi: 1105, terraform_module: 1106, generic: 1107 })
|
||||
expect(file_size_limits[:Plan1]).to eq({ conan: 1001, helm: 1008, maven: 1002, npm: 1003, nuget: 1004, pypi: 1005, terraform_module: 1006, generic: 1007 })
|
||||
expect(file_size_limits[:Plan2]).to eq({ conan: 1101, helm: 1108, maven: 1102, npm: 1103, nuget: 1104, pypi: 1105, terraform_module: 1106, generic: 1107 })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
|
|||
expect(json_response).to be_an Hash
|
||||
expect(json_response['conan_max_file_size']).to eq(Plan.default.actual_limits.conan_max_file_size)
|
||||
expect(json_response['generic_packages_max_file_size']).to eq(Plan.default.actual_limits.generic_packages_max_file_size)
|
||||
expect(json_response['helm_max_file_size']).to eq(Plan.default.actual_limits.helm_max_file_size)
|
||||
expect(json_response['maven_max_file_size']).to eq(Plan.default.actual_limits.maven_max_file_size)
|
||||
expect(json_response['npm_max_file_size']).to eq(Plan.default.actual_limits.npm_max_file_size)
|
||||
expect(json_response['nuget_max_file_size']).to eq(Plan.default.actual_limits.nuget_max_file_size)
|
||||
|
@ -45,6 +46,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
|
|||
expect(json_response).to be_an Hash
|
||||
expect(json_response['conan_max_file_size']).to eq(Plan.default.actual_limits.conan_max_file_size)
|
||||
expect(json_response['generic_packages_max_file_size']).to eq(Plan.default.actual_limits.generic_packages_max_file_size)
|
||||
expect(json_response['helm_max_file_size']).to eq(Plan.default.actual_limits.helm_max_file_size)
|
||||
expect(json_response['maven_max_file_size']).to eq(Plan.default.actual_limits.maven_max_file_size)
|
||||
expect(json_response['npm_max_file_size']).to eq(Plan.default.actual_limits.npm_max_file_size)
|
||||
expect(json_response['nuget_max_file_size']).to eq(Plan.default.actual_limits.nuget_max_file_size)
|
||||
|
@ -84,6 +86,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
|
|||
'plan_name': 'default',
|
||||
'conan_max_file_size': 10,
|
||||
'generic_packages_max_file_size': 20,
|
||||
'helm_max_file_size': 25,
|
||||
'maven_max_file_size': 30,
|
||||
'npm_max_file_size': 40,
|
||||
'nuget_max_file_size': 50,
|
||||
|
@ -95,6 +98,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
|
|||
expect(json_response).to be_an Hash
|
||||
expect(json_response['conan_max_file_size']).to eq(10)
|
||||
expect(json_response['generic_packages_max_file_size']).to eq(20)
|
||||
expect(json_response['helm_max_file_size']).to eq(25)
|
||||
expect(json_response['maven_max_file_size']).to eq(30)
|
||||
expect(json_response['npm_max_file_size']).to eq(40)
|
||||
expect(json_response['nuget_max_file_size']).to eq(50)
|
||||
|
@ -129,6 +133,7 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
|
|||
'plan_name': 'default',
|
||||
'conan_max_file_size': 'a',
|
||||
'generic_packages_max_file_size': 'b',
|
||||
'helm_max_file_size': 'h',
|
||||
'maven_max_file_size': 'c',
|
||||
'npm_max_file_size': 'd',
|
||||
'nuget_max_file_size': 'e',
|
||||
|
@ -140,8 +145,8 @@ RSpec.describe API::Admin::PlanLimits, 'PlanLimits' do
|
|||
expect(json_response['error']).to include(
|
||||
'conan_max_file_size is invalid',
|
||||
'generic_packages_max_file_size is invalid',
|
||||
'helm_max_file_size is invalid',
|
||||
'maven_max_file_size is invalid',
|
||||
'generic_packages_max_file_size is invalid',
|
||||
'npm_max_file_size is invalid',
|
||||
'nuget_max_file_size is invalid',
|
||||
'pypi_max_file_size is invalid',
|
||||
|
|
|
@ -131,8 +131,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
|||
let(:send_request) { subject }
|
||||
end
|
||||
|
||||
it 'updates runner info' do
|
||||
expect { subject }.to change { runner.reload.contacted_at }
|
||||
it "doesn't update runner info" do
|
||||
expect { subject }.not_to change { runner.reload.contacted_at }
|
||||
end
|
||||
|
||||
shared_examples 'authorizes local file' do
|
||||
|
@ -280,8 +280,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
it 'updates runner info' do
|
||||
expect { upload_artifacts(file_upload, headers_with_token) }.to change { runner.reload.contacted_at }
|
||||
it "doesn't update runner info" do
|
||||
expect { upload_artifacts(file_upload, headers_with_token) }.not_to change { runner.reload.contacted_at }
|
||||
end
|
||||
|
||||
context 'when the artifact is too large' do
|
||||
|
@ -812,8 +812,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
|||
let(:send_request) { download_artifact }
|
||||
end
|
||||
|
||||
it 'updates runner info' do
|
||||
expect { download_artifact }.to change { runner.reload.contacted_at }
|
||||
it "doesn't update runner info" do
|
||||
expect { download_artifact }.not_to change { runner.reload.contacted_at }
|
||||
end
|
||||
|
||||
context 'when job has artifacts' do
|
||||
|
|
|
@ -520,7 +520,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
|||
let(:head_response) { { status: :success } }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(DependencyProxy::FindOrCreateManifestService) do |instance|
|
||||
allow_next_instance_of(DependencyProxy::FindCachedManifestService) do |instance|
|
||||
allow(instance).to receive(:execute).and_return(pull_response)
|
||||
end
|
||||
allow_next_instance_of(DependencyProxy::HeadManifestService) do |instance|
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::StuckBuilds::DropPendingService do
|
||||
let!(:runner) { create :ci_runner }
|
||||
let!(:job) { create :ci_build, runner: runner }
|
||||
let_it_be(:runner) { create(:ci_runner) }
|
||||
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
|
||||
let_it_be_with_reload(:job) do
|
||||
create(:ci_build, pipeline: pipeline, runner: runner)
|
||||
end
|
||||
|
||||
let(:created_at) { }
|
||||
let(:updated_at) { }
|
||||
|
||||
|
@ -14,6 +18,8 @@ RSpec.describe Ci::StuckBuilds::DropPendingService do
|
|||
job_attributes = { status: status }
|
||||
job_attributes[:created_at] = created_at if created_at
|
||||
job_attributes[:updated_at] = updated_at if updated_at
|
||||
job_attributes.compact!
|
||||
|
||||
job.update!(job_attributes)
|
||||
end
|
||||
|
||||
|
@ -41,12 +47,6 @@ RSpec.describe Ci::StuckBuilds::DropPendingService do
|
|||
|
||||
it_behaves_like 'job is dropped with failure reason', 'stuck_or_timeout_failure'
|
||||
end
|
||||
|
||||
context 'when created_at is outside lookback window' do
|
||||
let(:created_at) { described_class::BUILD_LOOKBACK - 1.day }
|
||||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job was updated less than 1 day ago' do
|
||||
|
@ -63,12 +63,6 @@ RSpec.describe Ci::StuckBuilds::DropPendingService do
|
|||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
|
||||
context 'when created_at is outside lookback window' do
|
||||
let(:created_at) { described_class::BUILD_LOOKBACK - 1.day }
|
||||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job was updated more than 1 hour ago' do
|
||||
|
@ -85,12 +79,6 @@ RSpec.describe Ci::StuckBuilds::DropPendingService do
|
|||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
|
||||
context 'when created_at is outside lookback window' do
|
||||
let(:created_at) { described_class::BUILD_LOOKBACK - 1.day }
|
||||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -115,12 +103,6 @@ RSpec.describe Ci::StuckBuilds::DropPendingService do
|
|||
|
||||
it_behaves_like 'job is dropped with failure reason', 'stuck_or_timeout_failure'
|
||||
end
|
||||
|
||||
context 'when created_at is outside lookback window' do
|
||||
let(:created_at) { described_class::BUILD_LOOKBACK - 1.day }
|
||||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job was updated in less than 1 hour ago' do
|
||||
|
@ -137,12 +119,6 @@ RSpec.describe Ci::StuckBuilds::DropPendingService do
|
|||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
|
||||
context 'when created_at is outside lookback window' do
|
||||
let(:created_at) { described_class::BUILD_LOOKBACK - 1.day }
|
||||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -179,12 +155,6 @@ RSpec.describe Ci::StuckBuilds::DropPendingService do
|
|||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
|
||||
context 'when created_at is outside lookback window' do
|
||||
let(:created_at) { described_class::BUILD_LOOKBACK - 1.day }
|
||||
|
||||
it_behaves_like 'job is unchanged'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe DependencyProxy::FindOrCreateManifestService do
|
||||
RSpec.describe DependencyProxy::FindCachedManifestService do
|
||||
include DependencyProxyHelpers
|
||||
|
||||
let_it_be(:image) { 'alpine' }
|
||||
|
@ -49,14 +49,6 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do
|
|||
end
|
||||
|
||||
it_behaves_like 'returning no manifest'
|
||||
|
||||
context 'with dependency_proxy_manifest_workhorse feature disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_manifest_workhorse: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'downloading the manifest'
|
||||
end
|
||||
end
|
||||
|
||||
context 'failed head request' do
|
||||
|
@ -66,14 +58,6 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do
|
|||
end
|
||||
|
||||
it_behaves_like 'returning no manifest'
|
||||
|
||||
context 'with dependency_proxy_manifest_workhorse feature disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_manifest_workhorse: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'downloading the manifest'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -105,20 +89,6 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do
|
|||
end
|
||||
|
||||
it_behaves_like 'returning no manifest'
|
||||
|
||||
context 'with dependency_proxy_manifest_workhorse feature disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_manifest_workhorse: false)
|
||||
end
|
||||
|
||||
it 'downloads the new manifest and updates the existing record', :aggregate_failures do
|
||||
expect(subject[:status]).to eq(:success)
|
||||
expect(subject[:manifest]).to eq(dependency_proxy_manifest)
|
||||
expect(subject[:manifest].content_type).to eq(content_type)
|
||||
expect(subject[:manifest].digest).to eq(digest)
|
||||
expect(subject[:from_cache]).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the cached manifest is expired' do
|
||||
|
@ -129,14 +99,6 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do
|
|||
end
|
||||
|
||||
it_behaves_like 'returning no manifest'
|
||||
|
||||
context 'with dependency_proxy_manifest_workhorse feature disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_manifest_workhorse: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'downloading the manifest'
|
||||
end
|
||||
end
|
||||
|
||||
context 'failed connection' do
|
|
@ -1,77 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe DependencyProxy::PullManifestService do
|
||||
include DependencyProxyHelpers
|
||||
|
||||
let(:image) { 'alpine' }
|
||||
let(:tag) { '3.9' }
|
||||
let(:token) { Digest::SHA256.hexdigest('123') }
|
||||
let(:manifest) { { foo: 'bar' }.to_json }
|
||||
let(:digest) { '12345' }
|
||||
let(:content_type) { 'foo' }
|
||||
let(:headers) do
|
||||
{ DependencyProxy::Manifest::DIGEST_HEADER => digest, 'content-type' => content_type }
|
||||
end
|
||||
|
||||
subject { described_class.new(image, tag, token).execute_with_manifest(&method(:check_response)) }
|
||||
|
||||
context 'remote request is successful' do
|
||||
before do
|
||||
stub_manifest_download(image, tag, headers: headers)
|
||||
end
|
||||
|
||||
it 'successfully returns the manifest' do
|
||||
def check_response(response)
|
||||
response[:file].rewind
|
||||
|
||||
expect(response[:status]).to eq(:success)
|
||||
expect(response[:file].read).to eq(manifest)
|
||||
expect(response[:digest]).to eq(digest)
|
||||
expect(response[:content_type]).to eq(content_type)
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'remote request is not found' do
|
||||
before do
|
||||
stub_manifest_download(image, tag, status: 404, body: 'Not found')
|
||||
end
|
||||
|
||||
it 'returns a 404 not found error' do
|
||||
def check_response(response)
|
||||
expect(response[:status]).to eq(:error)
|
||||
expect(response[:http_status]).to eq(404)
|
||||
expect(response[:message]).to eq('Not found')
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'net timeout exception' do
|
||||
before do
|
||||
manifest_link = DependencyProxy::Registry.manifest_url(image, tag)
|
||||
|
||||
stub_full_request(manifest_link).to_timeout
|
||||
end
|
||||
|
||||
it 'returns a 599 error' do
|
||||
def check_response(response)
|
||||
expect(response[:status]).to eq(:error)
|
||||
expect(response[:http_status]).to eq(599)
|
||||
expect(response[:message]).to eq('execution expired')
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'no block is given' do
|
||||
subject { described_class.new(image, tag, token).execute_with_manifest }
|
||||
|
||||
it { expect { subject }.to raise_error(ArgumentError, 'Block must be provided') }
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue