Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-12-01 12:09:35 +00:00
parent 5a9468a4e5
commit 4ee706fcd1
81 changed files with 961 additions and 788 deletions

View File

@ -450,22 +450,6 @@ Cop/ActiveModelErrorsDirectManipulation:
Gitlab/AvoidFeatureGet:
Enabled: true
RSpec/TimecopFreeze:
Enabled: true
AutoCorrect: true
Include:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
- 'qa/spec/**/*.rb'
RSpec/TimecopTravel:
Enabled: true
AutoCorrect: true
Include:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
- 'qa/spec/**/*.rb'
RSpec/WebMockEnable:
Enabled: true
Include:

View File

@ -1,9 +0,0 @@
---
# Cop supports --autocorrect.
Rails/HttpStatus:
Exclude:
- 'app/controllers/concerns/invisible_captcha_on_signup.rb'
- 'app/controllers/projects/runner_projects_controller.rb'
- 'app/controllers/projects/service_ping_controller.rb'
- 'app/controllers/repositories/lfs_storage_controller.rb'
- 'ee/app/controllers/trials_controller.rb'

View File

@ -2,7 +2,7 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { captureException } from '@sentry/browser';
import PipelineWizard from '~/pipeline_wizard/pipeline_wizard.vue';
import PagesWizardTemplate from '~/pipeline_wizard/templates/pages.yml';
import PagesWizardTemplate from '~/pipeline_wizard/templates/pages.yml?raw';
import { logError } from '~/lib/logger';
import { s__ } from '~/locale';
import { redirectTo } from '~/lib/utils/url_utility';

View File

@ -200,11 +200,9 @@ export default {
class="no-expand gl-mr-3 gl-text-gray-900!"
:itemprop="microdata.nameItemprop"
>
{{
// ending bracket must be by closing tag to prevent
// link hover text-decoration from over-extending
group.name
}}
<!-- ending bracket must be by closing tag to prevent -->
<!-- link hover text-decoration from over-extending -->
{{ group.name }}
</a>
<gl-icon
v-gl-tooltip.hover.bottom

View File

@ -23,6 +23,12 @@ import ProjectSettingRow from './project_setting_row.vue';
const FEATURE_ACCESS_LEVEL_ANONYMOUS = [30, s__('ProjectSettings|Everyone')];
const PACKAGE_REGISTRY_ACCESS_LEVEL_DEFAULT_BY_PROJECT_VISIBILITY = {
[VISIBILITY_LEVEL_PRIVATE_INTEGER]: featureAccessLevel.PROJECT_MEMBERS,
[VISIBILITY_LEVEL_INTERNAL_INTEGER]: featureAccessLevel.EVERYONE,
[VISIBILITY_LEVEL_PUBLIC_INTEGER]: FEATURE_ACCESS_LEVEL_ANONYMOUS[0],
};
export default {
i18n: {
...CVE_ID_REQUEST_BUTTON_I18N,
@ -47,11 +53,15 @@ export default {
packagesHelpText: s__(
'ProjectSettings|Every project can have its own space to store its packages. Note: The Package Registry is always visible when a project is public.',
),
packageRegistryHelpText: s__(
'ProjectSettings|Every project can have its own space to store its packages.',
packageRegistryHelpText: s__('ProjectSettings|Publish, store, and view packages in a project.'),
packageRegistryForEveryoneHelpText: s__(
'ProjectSettings|Anyone can pull packages with a package manager API.',
),
packagesLabel: s__('ProjectSettings|Packages'),
packageRegistryLabel: s__('ProjectSettings|Package registry'),
packageRegistryForEveryoneLabel: s__(
'ProjectSettings|Allow anyone to pull from Package Registry',
),
pagesLabel: s__('ProjectSettings|Pages'),
ciCdLabel: __('CI/CD'),
repositoryLabel: s__('ProjectSettings|Repository'),
@ -287,18 +297,6 @@ export default {
);
},
packageRegistryFeatureAccessLevelOptions() {
const options = [FEATURE_ACCESS_LEVEL_ANONYMOUS];
if (this.visibilityLevel === VISIBILITY_LEVEL_PRIVATE_INTEGER) {
options.unshift(featureAccessLevelMembers);
} else if (this.visibilityLevel === VISIBILITY_LEVEL_INTERNAL_INTEGER) {
options.unshift(featureAccessLevelEveryone);
}
return options;
},
pagesFeatureAccessLevelOptions() {
const options = [featureAccessLevelMembers];
@ -366,6 +364,15 @@ export default {
packageRegistryAccessLevelEnabled() {
return this.glFeatures.packageRegistryAccessLevel;
},
packageRegistryEnabled() {
return this.packageRegistryAccessLevel > featureAccessLevel.NOT_ENABLED;
},
packageRegistryApiForEveryoneEnabled() {
return this.packageRegistryAccessLevel === FEATURE_ACCESS_LEVEL_ANONYMOUS[0];
},
packageRegistryApiForEveryoneEnabledShown() {
return this.visibilityLevel !== VISIBILITY_LEVEL_PUBLIC_INTEGER;
},
splitOperationsEnabled() {
return this.glFeatures.splitOperationsVisibilityPermissions;
},
@ -474,9 +481,8 @@ export default {
this.packageRegistryAccessLevelEnabled &&
this.packageRegistryAccessLevel === featureAccessLevel.PROJECT_MEMBERS
) {
this.packageRegistryAccessLevel = Math.min(
...this.packageRegistryFeatureAccessLevelOptions.map((option) => option[0]),
);
this.packageRegistryAccessLevel =
PACKAGE_REGISTRY_ACCESS_LEVEL_DEFAULT_BY_PROJECT_VISIBILITY[value];
}
if (this.buildsAccessLevel > featureAccessLevel.NOT_ENABLED)
this.buildsAccessLevel = featureAccessLevel.EVERYONE;
@ -561,6 +567,22 @@ export default {
visibilityAllowed(option) {
return this.allowedVisibilityOptions.includes(option);
},
onPackageRegistryEnabledToggle(value) {
this.packageRegistryAccessLevel = value
? this.packageRegistryAccessLevelDefault()
: featureAccessLevel.NOT_ENABLED;
},
onPackageRegistryApiForEveryoneEnabledToggle(value) {
this.packageRegistryAccessLevel = value
? FEATURE_ACCESS_LEVEL_ANONYMOUS[0]
: this.packageRegistryAccessLevelDefault();
},
packageRegistryAccessLevelDefault() {
return (
PACKAGE_REGISTRY_ACCESS_LEVEL_DEFAULT_BY_PROJECT_VISIBILITY[this.visibilityLevel] ??
featureAccessLevel.NOT_ENABLED
);
},
},
};
</script>
@ -897,10 +919,36 @@ export default {
:help-text="$options.i18n.packageRegistryHelpText"
data-testid="package-registry-access-level"
>
<project-feature-setting
v-model="packageRegistryAccessLevel"
<gl-toggle
class="gl-my-2"
:value="packageRegistryEnabled"
:label="$options.i18n.packageRegistryLabel"
:options="packageRegistryFeatureAccessLevelOptions"
label-position="hidden"
name="package_registry_enabled"
@change="onPackageRegistryEnabledToggle"
/>
<div
v-if="packageRegistryApiForEveryoneEnabledShown"
class="project-feature-setting-group gl-pl-7 gl-sm-pl-5 gl-my-3"
>
<project-setting-row
:label="$options.i18n.packageRegistryForEveryoneLabel"
:help-text="$options.i18n.packageRegistryForEveryoneHelpText"
>
<gl-toggle
class="gl-my-2"
:value="packageRegistryApiForEveryoneEnabled"
:disabled="!packageRegistryEnabled"
:label="$options.i18n.packageRegistryForEveryoneLabel"
label-position="hidden"
name="package_registry_api_for_everyone_enabled"
@change="onPackageRegistryApiForEveryoneEnabledToggle"
/>
</project-setting-row>
</div>
<input
:value="packageRegistryAccessLevel"
type="hidden"
name="project[project_feature_attributes][package_registry_access_level]"
/>
</project-setting-row>
@ -927,7 +975,7 @@ export default {
ref="monitor-settings"
:label="$options.i18n.monitorLabel"
:help-text="
s__('ProjectSettings|Configure your project resources and monitor their health.')
s__('ProjectSettings|Monitor the health of your project and respond to incidents.')
"
>
<project-feature-setting

View File

@ -252,7 +252,7 @@ export default {
@click="jobItemClick"
@mouseout="hideTooltips"
>
<div class="ci-job-name-component gl-display-flex gl-align-items-center">
<div class="gl-display-flex gl-align-items-center gl-flex-grow-1">
<ci-icon :size="24" :status="job.status" class="gl-line-height-0" />
<div class="gl-pl-3 gl-pr-3 gl-display-flex gl-flex-direction-column gl-pipeline-job-width">
<div class="gl-text-truncate gl-pr-9 gl-line-height-normal">{{ job.name }}</div>

View File

@ -29,7 +29,7 @@ export default {
};
</script>
<template>
<span class="ci-job-name-component mw-100 gl-display-flex gl-align-items-center">
<span class="mw-100 gl-display-flex gl-align-items-center gl-flex-grow-1">
<ci-icon :size="iconSize" :status="status" class="gl-line-height-0" />
<span class="gl-text-truncate mw-70p gl-pl-3 gl-display-inline-block">
{{ name }}

View File

@ -163,7 +163,7 @@ export default {
@click.stop="hideTooltips"
@mouseout="hideTooltips"
>
<job-name-component :name="job.name" :status="job.status" :icon-size="24" />
<job-name-component :name="job.name" :status="job.status" />
</gl-link>
<div
@ -175,7 +175,7 @@ export default {
data-testid="job-without-link"
@mouseout="hideTooltips"
>
<job-name-component :name="job.name" :status="job.status" :icon-size="24" />
<job-name-component :name="job.name" :status="job.status" />
</div>
<action-component

View File

@ -149,7 +149,7 @@ export default {
class="js-builds-dropdown-list scrollable-menu"
data-testid="mini-pipeline-graph-dropdown-menu-list"
>
<div class="gl--flex-center gl-border-b gl-font-weight-bold gl-pb-3">
<div class="gl--flex-center gl-border-b gl-font-weight-bold gl-mb-3 gl-pb-3">
<span class="gl-mr-1">{{ $options.i18n.stage }}</span>
<span data-testid="pipeline-stage-dropdown-menu-title">{{ stageName }}</span>
</div>

View File

@ -20,12 +20,12 @@ export default {
},
mixins: [glFeatureFlagsMixin()],
props: {
groupInitialData: {
groupInitialJson: {
type: Object,
required: false,
default: () => ({}),
},
projectInitialData: {
projectInitialJson: {
type: Object,
required: false,
default: () => ({}),
@ -72,11 +72,11 @@ export default {
</div>
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
<label class="gl-display-block">{{ __('Group') }}</label>
<group-filter :initial-data="groupInitialData" />
<group-filter :initial-data="groupInitialJson" />
</div>
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
<label class="gl-display-block">{{ __('Project') }}</label>
<project-filter :initial-data="projectInitialData" />
<project-filter :initial-data="projectInitialJson" />
</div>
</div>
<hr v-if="hasVerticalNav" class="gl-mt-5 gl-mb-0 gl-border-gray-100" />

View File

@ -11,10 +11,18 @@ export const initTopbar = (store) => {
return false;
}
let { groupInitialData, projectInitialData } = el.dataset;
const {
groupInitialJson,
projectInitialJson,
elasticsearchEnabled,
defaultBranchName,
} = el.dataset;
groupInitialData = JSON.parse(groupInitialData);
projectInitialData = JSON.parse(projectInitialData);
const groupInitialJsonParsed = JSON.parse(groupInitialJson);
const projectInitialJsonParsed = JSON.parse(projectInitialJson);
const elasticsearchEnabledParsed = elasticsearchEnabled
? JSON.parse(elasticsearchEnabled)
: false;
return new Vue({
el,
@ -22,8 +30,10 @@ export const initTopbar = (store) => {
render(createElement) {
return createElement(GlobalSearchTopbar, {
props: {
groupInitialData,
projectInitialData,
groupInitialJson: groupInitialJsonParsed,
projectInitialJson: projectInitialJsonParsed,
elasticsearchEnabled: elasticsearchEnabledParsed,
defaultBranchName,
},
});
},

View File

@ -822,7 +822,6 @@ Pipeline Graph
$ci-action-icon-size: 22px;
$ci-action-icon-size-lg: 24px;
$pipeline-dropdown-line-height: 20px;
$pipeline-dropdown-status-icon-size: 18px;
$ci-action-dropdown-button-size: 24px;
$ci-action-dropdown-svg-size: 12px;

View File

@ -33,8 +33,8 @@
.ci-action-icon-container {
position: absolute;
right: 8px;
top: 8px;
right: 11px;
top: 7px;
&.ci-action-icon-wrapper {
height: $ci-action-dropdown-button-size;
@ -84,25 +84,6 @@
&.non-details-job-component {
padding: $gl-padding-8 $gl-btn-horz-padding;
}
.ci-job-name-component {
align-items: center;
display: flex;
flex: 1;
}
.ci-status-icon {
position: relative;
> svg {
width: $pipeline-dropdown-status-icon-size;
height: $pipeline-dropdown-status-icon-size;
margin: 3px 0;
position: relative;
overflow: visible;
display: block;
}
}
}
// ensure .mini-pipeline-graph-dropdown-item has hover style when action-icon is hovered

View File

@ -2,25 +2,6 @@
// Please see the feedback issue for more details and help:
// https://gitlab.com/gitlab-org/gitlab/-/issues/331812
@charset "UTF-8";
:root {
color-scheme: dark;
}
body.gl-dark {
--gray-10: #1f1e24;
--gray-50: #333238;
--gray-100: #434248;
--gray-200: #535158;
--gray-700: #bfbfc3;
--gray-900: #ececef;
--green-100: #0d532a;
--green-700: #91d4a8;
--gl-text-color: #ececef;
--border-color: #4f4f4f;
--black: #fff;
}
:root {
--white: #333238;
}
*,
*::before,
*::after {
@ -1705,97 +1686,18 @@ svg.s16 {
}
:root {
color-scheme: dark;
}
body.gl-dark {
--gray-10: #1f1e24;
--gray-50: #333238;
--gray-100: #434248;
--gray-200: #535158;
--gray-300: #626168;
--gray-400: #737278;
--gray-500: #89888d;
--gray-600: #a4a3a8;
--gray-700: #bfbfc3;
--gray-800: #dcdcde;
--gray-900: #ececef;
--gray-950: #fbfafd;
--green-50: #0a4020;
--green-100: #0d532a;
--green-200: #24663b;
--green-300: #217645;
--green-400: #108548;
--green-500: #2da160;
--green-600: #52b87a;
--green-700: #91d4a8;
--green-800: #c3e6cd;
--green-900: #ecf4ee;
--green-950: #f1fdf6;
--blue-50: #033464;
--blue-100: #064787;
--blue-200: #0b5cad;
--blue-300: #1068bf;
--blue-400: #1f75cb;
--blue-500: #428fdc;
--blue-600: #63a6e9;
--blue-700: #9dc7f1;
--blue-800: #cbe2f9;
--blue-900: #e9f3fc;
--blue-950: #f2f9ff;
--orange-50: #5c2900;
--orange-100: #703800;
--orange-200: #8f4700;
--orange-300: #9e5400;
--orange-400: #ab6100;
--orange-500: #c17d10;
--orange-600: #d99530;
--orange-700: #e9be74;
--orange-800: #f5d9a8;
--orange-900: #fdf1dd;
--orange-950: #fff4e1;
--red-50: #660e00;
--red-100: #8d1300;
--red-200: #ae1800;
--red-300: #c91c00;
--red-400: #dd2b0e;
--red-500: #ec5941;
--red-600: #f57f6c;
--red-700: #fcb5aa;
--red-800: #fdd4cd;
--red-900: #fcf1ef;
--red-950: #fff4f3;
--indigo-50: #1a1a40;
--indigo-100: #292961;
--indigo-200: #393982;
--indigo-300: #4b4ba3;
--indigo-400: #5b5bbd;
--indigo-500: #6666c4;
--indigo-600: #7c7ccc;
--indigo-700: #a6a6de;
--indigo-800: #d1d1f0;
--indigo-900: #ebebfa;
--indigo-950: #f7f7ff;
--purple-50: #232150;
--purple-100: #2f2a6b;
--purple-200: #453894;
--purple-300: #5943b6;
--purple-400: #694cc0;
--purple-500: #7b58cf;
--purple-600: #9475db;
--purple-700: #ac93e6;
--purple-800: #cbbbf2;
--purple-900: #e1d8f9;
--purple-950: #f4f0ff;
--dark-icon-color-purple-1: #524a68;
--dark-icon-color-purple-2: #715bae;
--dark-icon-color-purple-3: #9a79f7;
--dark-icon-color-orange-1: #665349;
--dark-icon-color-orange-2: #b37a5d;
--gl-text-color: #ececef;
--border-color: #4f4f4f;
--border-color: #434248;
--white: #333238;
--black: #fff;
--gray-light: #333238;
--svg-status-bg: #333238;
}
.nav-sidebar,
.toggle-sidebar-button,
@ -1947,100 +1849,6 @@ body.gl-dark .navbar-gitlab .search form .search-input {
color: var(--gl-text-color);
}
:root {
color-scheme: dark;
}
body.gl-dark {
--gray-10: #1f1e24;
--gray-50: #333238;
--gray-100: #434248;
--gray-200: #535158;
--gray-300: #626168;
--gray-400: #737278;
--gray-500: #89888d;
--gray-600: #a4a3a8;
--gray-700: #bfbfc3;
--gray-800: #dcdcde;
--gray-900: #ececef;
--gray-950: #fbfafd;
--green-50: #0a4020;
--green-100: #0d532a;
--green-200: #24663b;
--green-300: #217645;
--green-400: #108548;
--green-500: #2da160;
--green-600: #52b87a;
--green-700: #91d4a8;
--green-800: #c3e6cd;
--green-900: #ecf4ee;
--green-950: #f1fdf6;
--blue-50: #033464;
--blue-100: #064787;
--blue-200: #0b5cad;
--blue-300: #1068bf;
--blue-400: #1f75cb;
--blue-500: #428fdc;
--blue-600: #63a6e9;
--blue-700: #9dc7f1;
--blue-800: #cbe2f9;
--blue-900: #e9f3fc;
--blue-950: #f2f9ff;
--orange-50: #5c2900;
--orange-100: #703800;
--orange-200: #8f4700;
--orange-300: #9e5400;
--orange-400: #ab6100;
--orange-500: #c17d10;
--orange-600: #d99530;
--orange-700: #e9be74;
--orange-800: #f5d9a8;
--orange-900: #fdf1dd;
--orange-950: #fff4e1;
--red-50: #660e00;
--red-100: #8d1300;
--red-200: #ae1800;
--red-300: #c91c00;
--red-400: #dd2b0e;
--red-500: #ec5941;
--red-600: #f57f6c;
--red-700: #fcb5aa;
--red-800: #fdd4cd;
--red-900: #fcf1ef;
--red-950: #fff4f3;
--indigo-50: #1a1a40;
--indigo-100: #292961;
--indigo-200: #393982;
--indigo-300: #4b4ba3;
--indigo-400: #5b5bbd;
--indigo-500: #6666c4;
--indigo-600: #7c7ccc;
--indigo-700: #a6a6de;
--indigo-800: #d1d1f0;
--indigo-900: #ebebfa;
--indigo-950: #f7f7ff;
--purple-50: #232150;
--purple-100: #2f2a6b;
--purple-200: #453894;
--purple-300: #5943b6;
--purple-400: #694cc0;
--purple-500: #7b58cf;
--purple-600: #9475db;
--purple-700: #ac93e6;
--purple-800: #cbbbf2;
--purple-900: #e1d8f9;
--purple-950: #f4f0ff;
--dark-icon-color-purple-1: #524a68;
--dark-icon-color-purple-2: #715bae;
--dark-icon-color-purple-3: #9a79f7;
--dark-icon-color-orange-1: #665349;
--dark-icon-color-orange-2: #b37a5d;
--gl-text-color: #ececef;
--border-color: #4f4f4f;
--white: #333238;
--black: #fff;
--gray-light: #333238;
--svg-status-bg: #333238;
}
.tab-width-8 {
tab-size: 8;
}

View File

@ -113,141 +113,6 @@ $data-viz-blue-800: #b7c6ff;
$data-viz-blue-900: #d2dcff;
$data-viz-blue-950: #e9ebff;
:root {
color-scheme: dark;
}
body.gl-dark {
--gray-10: #{$gray-10};
--gray-50: #{$gray-50};
--gray-100: #{$gray-100};
--gray-200: #{$gray-200};
--gray-300: #{$gray-300};
--gray-400: #{$gray-400};
--gray-500: #{$gray-500};
--gray-600: #{$gray-600};
--gray-700: #{$gray-700};
--gray-800: #{$gray-800};
--gray-900: #{$gray-900};
--gray-950: #{$gray-950};
--green-50: #{$green-50};
--green-100: #{$green-100};
--green-200: #{$green-200};
--green-300: #{$green-300};
--green-400: #{$green-400};
--green-500: #{$green-500};
--green-600: #{$green-600};
--green-700: #{$green-700};
--green-800: #{$green-800};
--green-900: #{$green-900};
--green-950: #{$green-950};
--blue-50: #{$blue-50};
--blue-100: #{$blue-100};
--blue-200: #{$blue-200};
--blue-300: #{$blue-300};
--blue-400: #{$blue-400};
--blue-500: #{$blue-500};
--blue-600: #{$blue-600};
--blue-700: #{$blue-700};
--blue-800: #{$blue-800};
--blue-900: #{$blue-900};
--blue-950: #{$blue-950};
--orange-50: #{$orange-50};
--orange-100: #{$orange-100};
--orange-200: #{$orange-200};
--orange-300: #{$orange-300};
--orange-400: #{$orange-400};
--orange-500: #{$orange-500};
--orange-600: #{$orange-600};
--orange-700: #{$orange-700};
--orange-800: #{$orange-800};
--orange-900: #{$orange-900};
--orange-950: #{$orange-950};
--red-50: #{$red-50};
--red-100: #{$red-100};
--red-200: #{$red-200};
--red-300: #{$red-300};
--red-400: #{$red-400};
--red-500: #{$red-500};
--red-600: #{$red-600};
--red-700: #{$red-700};
--red-800: #{$red-800};
--red-900: #{$red-900};
--red-950: #{$red-950};
--indigo-50: #{$indigo-50};
--indigo-100: #{$indigo-100};
--indigo-200: #{$indigo-200};
--indigo-300: #{$indigo-300};
--indigo-400: #{$indigo-400};
--indigo-500: #{$indigo-500};
--indigo-600: #{$indigo-600};
--indigo-700: #{$indigo-700};
--indigo-800: #{$indigo-800};
--indigo-900: #{$indigo-900};
--indigo-950: #{$indigo-950};
--purple-50: #{$purple-50};
--purple-100: #{$purple-100};
--purple-200: #{$purple-200};
--purple-300: #{$purple-300};
--purple-400: #{$purple-400};
--purple-500: #{$purple-500};
--purple-600: #{$purple-600};
--purple-700: #{$purple-700};
--purple-800: #{$purple-800};
--purple-900: #{$purple-900};
--purple-950: #{$purple-950};
--dark-icon-color-purple-1: #524a68;
--dark-icon-color-purple-2: #715bae;
--dark-icon-color-purple-3: #9a79f7;
--dark-icon-color-orange-1: #665349;
--dark-icon-color-orange-2: #b37a5d;
--gl-text-color: #{$gray-900};
--border-color: #{$border-color};
--white: #{$white};
--black: #{$black};
--gray-light: #{$gray-50};
--svg-status-bg: #{$white};
.gl-button.gl-button,
.gl-button.gl-button.btn-block {
&.btn-default,
&.btn-dashed,
&.btn-info,
&.btn-success,
&.btn-danger,
&.btn-confirm {
&-tertiary {
mix-blend-mode: screen;
}
}
}
.gl-datepicker-theme {
.pika-prev,
.pika-next {
filter: invert(0.9);
}
.is-selected > .pika-button {
color: $gray-900;
}
:not(.is-selected) > .pika-button:hover {
background-color: $gray-200;
}
}
}
$border-white-normal: $border-color;
$body-bg: $gray-10;

View File

@ -2,6 +2,140 @@
@import 'page_bundles/mixins_and_variables_and_functions';
@import './themes/theme_helper';
:root {
color-scheme: dark;
--gray-10: #{$gray-10};
--gray-50: #{$gray-50};
--gray-100: #{$gray-100};
--gray-200: #{$gray-200};
--gray-300: #{$gray-300};
--gray-400: #{$gray-400};
--gray-500: #{$gray-500};
--gray-600: #{$gray-600};
--gray-700: #{$gray-700};
--gray-800: #{$gray-800};
--gray-900: #{$gray-900};
--gray-950: #{$gray-950};
--green-50: #{$green-50};
--green-100: #{$green-100};
--green-200: #{$green-200};
--green-300: #{$green-300};
--green-400: #{$green-400};
--green-500: #{$green-500};
--green-600: #{$green-600};
--green-700: #{$green-700};
--green-800: #{$green-800};
--green-900: #{$green-900};
--green-950: #{$green-950};
--blue-50: #{$blue-50};
--blue-100: #{$blue-100};
--blue-200: #{$blue-200};
--blue-300: #{$blue-300};
--blue-400: #{$blue-400};
--blue-500: #{$blue-500};
--blue-600: #{$blue-600};
--blue-700: #{$blue-700};
--blue-800: #{$blue-800};
--blue-900: #{$blue-900};
--blue-950: #{$blue-950};
--orange-50: #{$orange-50};
--orange-100: #{$orange-100};
--orange-200: #{$orange-200};
--orange-300: #{$orange-300};
--orange-400: #{$orange-400};
--orange-500: #{$orange-500};
--orange-600: #{$orange-600};
--orange-700: #{$orange-700};
--orange-800: #{$orange-800};
--orange-900: #{$orange-900};
--orange-950: #{$orange-950};
--red-50: #{$red-50};
--red-100: #{$red-100};
--red-200: #{$red-200};
--red-300: #{$red-300};
--red-400: #{$red-400};
--red-500: #{$red-500};
--red-600: #{$red-600};
--red-700: #{$red-700};
--red-800: #{$red-800};
--red-900: #{$red-900};
--red-950: #{$red-950};
--indigo-50: #{$indigo-50};
--indigo-100: #{$indigo-100};
--indigo-200: #{$indigo-200};
--indigo-300: #{$indigo-300};
--indigo-400: #{$indigo-400};
--indigo-500: #{$indigo-500};
--indigo-600: #{$indigo-600};
--indigo-700: #{$indigo-700};
--indigo-800: #{$indigo-800};
--indigo-900: #{$indigo-900};
--indigo-950: #{$indigo-950};
--purple-50: #{$purple-50};
--purple-100: #{$purple-100};
--purple-200: #{$purple-200};
--purple-300: #{$purple-300};
--purple-400: #{$purple-400};
--purple-500: #{$purple-500};
--purple-600: #{$purple-600};
--purple-700: #{$purple-700};
--purple-800: #{$purple-800};
--purple-900: #{$purple-900};
--purple-950: #{$purple-950};
--dark-icon-color-purple-1: #524a68;
--dark-icon-color-purple-2: #715bae;
--dark-icon-color-purple-3: #9a79f7;
--dark-icon-color-orange-1: #665349;
--dark-icon-color-orange-2: #b37a5d;
--gl-text-color: #{$gray-900};
--border-color: #{$border-color};
--white: #{$white};
--black: #{$black};
--gray-light: #{$gray-50};
--svg-status-bg: #{$white};
}
.gl-dark {
.gl-button.gl-button,
.gl-button.gl-button.btn-block {
&.btn-default,
&.btn-dashed,
&.btn-info,
&.btn-success,
&.btn-danger,
&.btn-confirm {
&-tertiary {
mix-blend-mode: screen;
}
}
}
.gl-datepicker-theme {
.pika-prev,
.pika-next {
filter: invert(0.9);
}
.is-selected > .pika-button {
color: $gray-900;
}
:not(.is-selected) > .pika-button:hover {
background-color: $gray-200;
}
}
}
// Some hacks and overrides for things that don't properly support dark mode
.gl-label {
filter: brightness(0.9) contrast(1.1);

View File

@ -13,7 +13,7 @@ module InvisibleCaptchaOnSignup
invisible_captcha_honeypot_counter.increment
log_request('Invisible_Captcha_Honeypot_Request')
head(200)
head(:ok)
end
def on_timestamp_spam_callback

View File

@ -7,7 +7,7 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
skip_cross_project_access_check :index
feature_category :snippets
feature_category :source_code_management
def index
@snippet_counts = Snippets::CountService

View File

@ -3,7 +3,7 @@
class Explore::SnippetsController < Explore::ApplicationController
include Gitlab::NoteableMetadata
feature_category :snippets
feature_category :source_code_management
def index
@snippets = SnippetsFinder.new(current_user, explore: true)

View File

@ -7,7 +7,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
feature_category :team_planning, [:issues, :labels, :milestones, :commands, :contacts]
feature_category :code_review, [:merge_requests]
feature_category :users, [:members]
feature_category :snippets, [:snippets]
feature_category :source_code_management, [:snippets]
urgency :low, [:merge_requests, :members]
urgency :low, [:issues, :labels, :milestones, :commands, :contacts]

View File

@ -11,7 +11,7 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) unless can?(current_user, :assign_runner, @runner)
return head(:forbidden) unless can?(current_user, :assign_runner, @runner)
path = project_runners_path(project)

View File

@ -10,7 +10,7 @@ class Projects::ServicePingController < Projects::ApplicationController
Gitlab::UsageDataCounters::WebIdeCounter.increment_previews_count
head(200)
head(:ok)
end
def web_ide_clientside_preview_success
@ -20,12 +20,12 @@ class Projects::ServicePingController < Projects::ApplicationController
Gitlab::UsageDataCounters::EditorUniqueCounter.track_live_preview_edit_action(author: current_user,
project: project)
head(200)
head(:ok)
end
def web_ide_pipelines_count
Gitlab::UsageDataCounters::WebIdeCounter.increment_pipelines_count
head(200)
head(:ok)
end
end

View File

@ -4,7 +4,7 @@ class Projects::Snippets::ApplicationController < Projects::ApplicationControlle
include FindSnippet
include SnippetAuthorizations
feature_category :snippets
feature_category :source_code_management
private

View File

@ -49,7 +49,7 @@ module Repositories
validate_uploaded_file!
if store_file!(oid, size)
head 200, content_type: LfsRequest::CONTENT_TYPE
head :ok, content_type: LfsRequest::CONTENT_TYPE
else
render plain: 'Unprocessable entity', status: :unprocessable_entity
end

View File

@ -32,7 +32,7 @@ class SearchController < ApplicationController
before_action only: :show do
push_frontend_feature_flag(:search_page_vertical_nav, current_user)
end
before_action :elasticsearch_in_use, only: :show
rescue_from ActiveRecord::QueryCanceled, with: :render_timeout
layout 'search'
@ -118,6 +118,11 @@ class SearchController < ApplicationController
def opensearch
end
def elasticsearch_in_use
search_service.respond_to?(:use_elasticsearch?) && search_service.use_elasticsearch?
end
strong_memoize_attr :elasticsearch_in_use
private
# overridden in EE

View File

@ -4,7 +4,7 @@ class Snippets::ApplicationController < ApplicationController
include FindSnippet
include SnippetAuthorizations
feature_category :snippets
feature_category :source_code_management
private

View File

@ -8,7 +8,7 @@ class Snippets::NotesController < ApplicationController
before_action :authorize_read_snippet!, only: [:show, :index]
before_action :authorize_create_note!, only: [:create]
feature_category :snippets
feature_category :source_code_management
private

View File

@ -31,8 +31,7 @@ class UsersController < ApplicationController
:followers, :following, :calendar, :calendar_activities,
:exists, :activity, :follow, :unfollow, :ssh_keys]
feature_category :snippets, [:snippets]
feature_category :source_code_management, [:gpg_keys]
feature_category :source_code_management, [:snippets, :gpg_keys]
# TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/357914
urgency :low, [:show, :calendar_activities, :contributed, :activity, :projects, :groups, :calendar, :snippets]

View File

@ -58,16 +58,16 @@ class User < ApplicationRecord
add_authentication_token_field :feed_token
add_authentication_token_field :static_object_token, encrypted: :optional
default_value_for :admin, false
default_value_for(:external) { Gitlab::CurrentSettings.user_default_external }
default_value_for(:can_create_group) { Gitlab::CurrentSettings.can_create_group }
default_value_for :can_create_team, false
default_value_for :hide_no_ssh_key, false
default_value_for :hide_no_password, false
default_value_for :project_view, :files
default_value_for :notified_of_own_activity, false
default_value_for :preferred_language, I18n.default_locale
default_value_for :theme_id, gitlab_config.default_theme
attribute :admin, default: false
attribute :external, default: -> { Gitlab::CurrentSettings.user_default_external }
attribute :can_create_group, default: -> { Gitlab::CurrentSettings.can_create_group }
attribute :can_create_team, default: false
attribute :hide_no_ssh_key, default: false
attribute :hide_no_password, default: false
attribute :project_view, default: :files
attribute :notified_of_own_activity, default: false
attribute :preferred_language, default: -> { I18n.default_locale }
attribute :theme_id, default: -> { gitlab_config.default_theme }
attr_encrypted :otp_secret,
key: Gitlab::Application.secrets.otp_key_base,
@ -376,6 +376,14 @@ class User < ApplicationRecord
accepts_nested_attributes_for :credit_card_validation, update_only: true, allow_destroy: true
state_machine :state, initial: :active do
# state_machine uses this method at class loading time to fetch the default
# value for the `state` column but in doing so it also evaluates all other
# columns default values which could trigger the recursive generation of
# ApplicationSetting records. We're setting it to `nil` here because we
# don't have a database default for the `state` column.
#
def owner_class_attribute_default; end
event :block do
transition active: :blocked
transition deactivated: :blocked

View File

@ -26,10 +26,10 @@ class UserPreference < ApplicationRecord
ignore_columns :experience_level, remove_with: '14.10', remove_after: '2021-03-22'
default_value_for :tab_width, value: Gitlab::TabWidth::DEFAULT, allows_nil: false
default_value_for :time_display_relative, value: true, allows_nil: false
default_value_for :time_format_in_24h, value: false, allows_nil: false
default_value_for :render_whitespace_in_code, value: false, allows_nil: false
attribute :tab_width, default: -> { Gitlab::TabWidth::DEFAULT }
attribute :time_display_relative, default: true
attribute :time_format_in_24h, default: false
attribute :render_whitespace_in_code, default: false
class << self
def notes_filters
@ -59,6 +59,67 @@ class UserPreference < ApplicationRecord
self[notes_filter_field_for(resource)]
end
def tab_width
read_attribute(:tab_width) || self.class.column_defaults['tab_width']
end
def tab_width=(value)
if value.nil?
default = self.class.column_defaults['tab_width']
super(default)
else
super(value)
end
end
def time_display_relative
value = read_attribute(:time_display_relative)
return value unless value.nil?
self.class.column_defaults['time_display_relative']
end
def time_display_relative=(value)
if value.nil?
default = self.class.column_defaults['time_display_relative']
super(default)
else
super(value)
end
end
def time_format_in_24h
value = read_attribute(:time_format_in_24h)
return value unless value.nil?
self.class.column_defaults['time_format_in_24h']
end
def time_format_in_24h=(value)
if value.nil?
default = self.class.column_defaults['time_format_in_24h']
super(default)
else
super(value)
end
end
def render_whitespace_in_code
value = read_attribute(:render_whitespace_in_code)
return value unless value.nil?
self.class.column_defaults['render_whitespace_in_code']
end
def render_whitespace_in_code=(value)
if value.nil?
default = self.class.column_defaults['render_whitespace_in_code']
super(default)
else
super(value)
end
end
private
def notes_filter_field_for(resource)

View File

@ -19,6 +19,13 @@ module ChatNames
# rubocop: disable CodeReuse/ActiveRecord
def find_chat_name
if @integration.nil?
return ChatName.find_by(
team_id: @params[:team_id],
chat_id: @params[:user_id]
)
end
ChatName.find_by(
integration: @integration,
team_id: @params[:team_id],

View File

@ -20,7 +20,7 @@
= render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' }
.gl-mt-3
#js-search-topbar{ data: { "group-initial-data": group_attributes.to_json, "project-initial-data": project_attributes.to_json } }
#js-search-topbar{ data: { "group-initial-json": group_attributes.to_json, "project-initial-json": project_attributes.to_json, "elasticsearch-enabled": @elasticsearch_in_use.to_s, "default-branch-name": @project&.default_branch } }
- if @search_term
- if Feature.disabled?(:search_page_vertical_nav, current_user)
= render 'search/category'

View File

@ -67,7 +67,6 @@
- geo_replication
- git_lfs
- gitaly
- gitlab_docs
- global_search
- helm_chart_registry
- importers
@ -105,6 +104,7 @@
- pubsec_services
- purchase
- quality_management
- rate_limiting
- redis
- release_evidence
- release_orchestration
@ -116,6 +116,7 @@
- runner_fleet
- runner_saas
- saas_provisioning
- sbom
- scalability
- secret_detection
- secrets_management
@ -124,7 +125,6 @@
- service_desk
- service_ping
- sm_provisioning
- snippets
- source_code_management
- static_application_security_testing
- subgroups

View File

@ -435,6 +435,7 @@ module.exports = {
},
{
test: /\.(yml|yaml)$/,
resourceQuery: /raw/,
loader: 'raw-loader',
},
].filter(Boolean),

View File

@ -31,7 +31,7 @@ POST /projects/:id/product_analytics/request/dry-run
### Request body
The body of the load request should be a valid Cube query.
The body of the load request must be a valid Cube query.
```json
{
@ -68,9 +68,9 @@ The body of the load request should be a valid Cube query.
}
```
## Send meta request to Cube
## Send metadata request to Cube
Returns Cube Meta data for the Analytics data. For example:
Return Cube Metadata for the Analytics data. For example:
```plaintext
GET /projects/:id/product_analytics/request/meta

View File

@ -225,18 +225,7 @@ and clear the user from the list of assignees, or select **Unassigned**.
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
You can manually create [To-Do list items](../../user/todos.md) for yourself
from the Alert details screen, and view them later on your **To-Do List**. To
add a to-do item:
You can manually create a [to-do item](../../user/todos.md) for yourself
from an alert, and view it later on your **To-Do List**.
1. Display the list of current alerts:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Monitor > Alerts**.
1. Select your desired alert to display its **Alert Management Details View**.
1. On the right sidebar, select **Add a to do**:
![Alert Details Add a to do](img/alert_detail_add_todo_v13_9.png)
To view your To-Do List, on the top bar, select **To-Do List** (**{todo-done}**).
To add a to-do item, on the right sidebar, select **Add a to do**.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -32,24 +32,24 @@ Prerequisite:
1. Select **Enable product analytics** and enter the configuration values.
The following table shows the required configuration parameters and example values:
| Name | Value |
|------------------------------|----------------------------|
| Jitsu host | `https://jitsu.gitlab.com` |
| Jitsu project ID | `g0maofw84gx5sjxgse2k` |
| Jitsu administrator email | `jitsu.admin@gitlab.com` |
| Jitsu administrator password | `<your_password>` |
| Name | Value |
|------------------------------|------------------------------------------------------------|
| Jitsu host | `https://jitsu.gitlab.com` |
| Jitsu project ID | `g0maofw84gx5sjxgse2k` |
| Jitsu administrator email | `jitsu.admin@gitlab.com` |
| Jitsu administrator password | `<your_password>` |
| Clickhouse URL | `https://<username>:<password>@clickhouse.gitlab.com:8123` |
| Cube API URL | `https://cube.gitlab.com` |
| Cube API key | `25718201b3e9...ae6bbdc62dbb` |
| Cube API URL | `https://cube.gitlab.com` |
| Cube API key | `25718201b3e9...ae6bbdc62dbb` |
1. Select **Save changes**.
## Product analytics dashboards
Each project can define an unlimited number of dashboards. These dashboards are defined using our YAML schema and stored
in the `.gitlab/product_analytics/dashboards/` directory. The name of the file is the name of the dashboard, and visualizations are shared across dashboards..
in the `.gitlab/product_analytics/dashboards/` directory of a project repository. The name of the file is the name of the dashboard, and visualizations are shared across dashboards.
Project maintainers can enforce approval rules on dashboard changes, and dashboards can be versioned in source control.
Project maintainers can enforce approval rules on dashboard changes using features such as code owners and approval rules. Dashboards are versioned in source control with the rest of a project's code.
### Define a dashboard
@ -57,8 +57,8 @@ To define a dashboard:
1. In `.gitlab/product_analytics/dashboards/`, create a directory named like the dashboard. Each dashboard should have its own directory.
1. In the new directory, create a `.yaml` file with the same name as the directory. This file contains the dashboard definition, and must conform to the JSON schema defined in `ee/app/validators/json_schemas/product_analytics_dashboard.json`.
1. In the `.gitlab/product_analytics/dashboards/visualizations/` directory, create a `yaml` file. This file defines the visualization type for the dashboard, and must conform to the schema in
`ee/app/validators/json_schemas/product_analytics_visualization.json`.
1. In the `.gitlab/product_analytics/dashboards/visualizations/` directory, create a `yaml` file. This file defines the visualization type for the dashboard, and must conform to the schema in
`ee/app/validators/json_schemas/product_analytics_visualization.json`.
The example below includes three dashboards and one visualization that applies to all dashboards.

View File

@ -43,6 +43,8 @@ module.exports = (path, options = {}) => {
const TEST_FIXTURES_PATTERN = 'test_fixtures(/.*)$';
const moduleNameMapper = {
'^~(/.*)\\?raw$': '<rootDir>/app/assets/javascripts$1',
'^(.*)\\?raw$': '$1',
'^~(/.*)$': '<rootDir>/app/assets/javascripts$1',
'^ee_component(/.*)$':
'<rootDir>/app/assets/javascripts/vue_shared/components/empty_component.js',

View File

@ -7,7 +7,7 @@ module API
[
{ type: 'issue', resource: :projects, find_by: :iid, feature_category: :team_planning },
{ type: 'merge_request', resource: :projects, find_by: :iid, feature_category: :code_review },
{ type: 'snippet', resource: :projects, find_by: :id, feature_category: :snippets }
{ type: 'snippet', resource: :projects, find_by: :id, feature_category: :source_code_management }
]
end

View File

@ -8,7 +8,7 @@ module API
# extend it.
{
Issue => :team_planning,
Snippet => :snippets,
Snippet => :source_code_management,
MergeRequest => :code_review,
Commit => :code_review
}

View File

@ -9,7 +9,7 @@ module API
{
Issue => :team_planning,
MergeRequest => :code_review,
Snippet => :snippets
Snippet => :source_code_management
}
end

View File

@ -6,7 +6,7 @@ module API
before { check_snippets_enabled }
feature_category :snippets
feature_category :source_code_management
params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'

View File

@ -5,7 +5,7 @@ module API
class Snippets < ::API::Base
include PaginationParams
feature_category :snippets
feature_category :source_code_management
urgency :low
resource :snippets do

View File

@ -50,8 +50,6 @@ module Gitlab
def initialize
@configuration = Configuration.new
@alive = true
init_prometheus_metrics
end
##
@ -62,7 +60,7 @@ module Gitlab
end
def call
logger.info(log_labels.merge(message: 'started'))
event_reporter.started(log_labels)
while @alive
sleep(sleep_time_seconds)
@ -70,7 +68,7 @@ module Gitlab
monitor if Feature.enabled?(:gitlab_memory_watchdog, type: :ops)
end
logger.info(log_labels.merge(message: 'stopped'))
event_reporter.stopped(log_labels)
end
def stop
@ -85,18 +83,16 @@ module Gitlab
next unless result.threshold_violated?
@counter_violations.increment(reason: result.monitor_name)
event_reporter.threshold_violated(result.monitor_name)
next unless result.strikes_exceeded?
@alive = !memory_limit_exceeded_callback(result.monitor_name, result.payload)
@alive = !strike_exceeded_callback(result.monitor_name, result.payload)
end
end
def memory_limit_exceeded_callback(monitor_name, monitor_payload)
all_labels = log_labels.merge(monitor_payload)
logger.warn(all_labels)
@counter_violations_handled.increment(reason: monitor_name)
def strike_exceeded_callback(monitor_name, monitor_payload)
event_reporter.strikes_exceeded(monitor_name, log_labels(monitor_payload))
Gitlab::Memory::Reports::HeapDump.enqueue! if @configuration.write_heap_dumps?
@ -111,43 +107,18 @@ module Gitlab
@configuration.handler
end
def logger
@configuration.logger
def event_reporter
@configuration.event_reporter
end
def sleep_time_seconds
@configuration.sleep_time_seconds
end
def log_labels
{
pid: $$,
worker_id: worker_id,
def log_labels(extra = {})
extra.merge(
memwd_handler_class: handler.class.name,
memwd_sleep_time_s: sleep_time_seconds,
memwd_rss_bytes: process_rss_bytes
}
end
def process_rss_bytes
Gitlab::Metrics::System.memory_usage_rss[:total]
end
def worker_id
::Prometheus::PidProvider.worker_id
end
def init_prometheus_metrics
default_labels = { pid: worker_id }
@counter_violations = Gitlab::Metrics.counter(
:gitlab_memwd_violations_total,
'Total number of times a Ruby process violated a memory threshold',
default_labels
)
@counter_violations_handled = Gitlab::Metrics.counter(
:gitlab_memwd_violations_handled_total,
'Total number of times Ruby process memory violations were handled',
default_labels
memwd_sleep_time_s: sleep_time_seconds
)
end
end

View File

@ -35,7 +35,7 @@ module Gitlab
DEFAULT_SLEEP_TIME_SECONDS = 60
attr_writer :logger, :handler, :sleep_time_seconds, :write_heap_dumps
attr_writer :event_reporter, :handler, :sleep_time_seconds, :write_heap_dumps
def monitors
@monitor_stack ||= MonitorStack.new
@ -47,8 +47,8 @@ module Gitlab
@handler ||= NullHandler.instance
end
def logger
@logger ||= Gitlab::Logger.new($stdout)
def event_reporter
@event_reporter ||= EventReporter.new
end
# Used to control the frequency with which the watchdog will wake up and poll the GC.

View File

@ -17,7 +17,6 @@ module Gitlab
class << self
def configure_for_puma
->(config) do
config.logger = Gitlab::AppLogger
config.handler = Gitlab::Memory::Watchdog::PumaHandler.new
config.write_heap_dumps = write_heap_dumps?
config.sleep_time_seconds = ENV.fetch('GITLAB_MEMWD_SLEEP_TIME_SEC', DEFAULT_SLEEP_INTERVAL_S).to_i
@ -27,11 +26,11 @@ module Gitlab
def configure_for_sidekiq
->(config) do
config.logger = Sidekiq.logger
config.handler = Gitlab::Memory::Watchdog::TermProcessHandler.new
config.write_heap_dumps = write_heap_dumps?
config.sleep_time_seconds = sidekiq_sleep_time
config.monitors(&configure_monitors_for_sidekiq)
config.event_reporter = EventReporter.new(logger: ::Sidekiq.logger)
end
end

View File

@ -0,0 +1,73 @@
# frozen_string_literal: true
module Gitlab
module Memory
class Watchdog
class EventReporter
include ::Gitlab::Utils::StrongMemoize
attr_reader :logger
def initialize(logger: Gitlab::AppLogger)
@logger = logger
end
def started(labels = {})
logger.info(message: 'started', **log_labels(labels))
end
def stopped(labels = {})
logger.info(message: 'stopped', **log_labels(labels))
end
def threshold_violated(monitor_name)
counter_violations.increment(reason: monitor_name)
end
def strikes_exceeded(monitor_name, labels = {})
logger.warn(log_labels(labels))
counter_violations_handled.increment(reason: monitor_name)
end
private
def log_labels(extra = {})
extra.merge(
pid: $$,
worker_id: worker_id,
memwd_rss_bytes: process_rss_bytes
)
end
def process_rss_bytes
Gitlab::Metrics::System.memory_usage_rss[:total]
end
def worker_id
::Prometheus::PidProvider.worker_id
end
def counter_violations
strong_memoize("counter_violations") do
::Gitlab::Metrics.counter(
:gitlab_memwd_violations_total,
'Total number of times a Ruby process violated a memory threshold',
{ pid: worker_id }
)
end
end
def counter_violations_handled
strong_memoize("counter_violations_handled") do
::Gitlab::Metrics.counter(
:gitlab_memwd_violations_handled_total,
'Total number of times Ruby process memory violations were handled',
{ pid: worker_id }
)
end
end
end
end
end
end

View File

@ -27129,6 +27129,9 @@ msgstr ""
msgid "New incident"
msgstr ""
msgid "New incident has been created"
msgstr ""
msgid "New issue"
msgstr ""
@ -32209,12 +32212,18 @@ msgstr ""
msgid "ProjectSettings|Allow"
msgstr ""
msgid "ProjectSettings|Allow anyone to pull from Package Registry"
msgstr ""
msgid "ProjectSettings|Always show thumbs-up and thumbs-down award emoji buttons on issues, merge requests, and snippets."
msgstr ""
msgid "ProjectSettings|Analytics"
msgstr ""
msgid "ProjectSettings|Anyone can pull packages with a package manager API."
msgstr ""
msgid "ProjectSettings|Auto-close referenced issues on default branch"
msgstr ""
@ -32296,9 +32305,6 @@ msgstr ""
msgid "ProjectSettings|Every project can have its own space to store its Docker images"
msgstr ""
msgid "ProjectSettings|Every project can have its own space to store its packages."
msgstr ""
msgid "ProjectSettings|Every project can have its own space to store its packages. Note: The Package Registry is always visible when a project is public."
msgstr ""
@ -32413,6 +32419,9 @@ msgstr ""
msgid "ProjectSettings|Monitor"
msgstr ""
msgid "ProjectSettings|Monitor the health of your project and respond to incidents."
msgstr ""
msgid "ProjectSettings|No merge commits are created."
msgstr ""
@ -32458,6 +32467,9 @@ msgstr ""
msgid "ProjectSettings|Public"
msgstr ""
msgid "ProjectSettings|Publish, store, and view packages in a project."
msgstr ""
msgid "ProjectSettings|Releases"
msgstr ""
@ -38667,6 +38679,9 @@ msgstr ""
msgid "Something went wrong when reordering designs. Please try again"
msgstr ""
msgid "Something went wrong when sending the incident link to Slack."
msgstr ""
msgid "Something went wrong while adding timeline event."
msgstr ""
@ -41488,6 +41503,9 @@ msgstr ""
msgid "There was a problem communicating with your device."
msgstr ""
msgid "There was a problem creating the incident. Please try again."
msgstr ""
msgid "There was a problem fetching CRM contacts."
msgstr ""

View File

@ -54,7 +54,7 @@
"@cubejs-client/core": "^0.31.0",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "3.11.0",
"@gitlab/svgs": "3.12.0",
"@gitlab/ui": "50.1.2",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20221114183058",

View File

@ -1,41 +0,0 @@
# frozen_string_literal: true
require 'rubocop-rspec'
module RuboCop
module Cop
module RSpec
# This cop checks for `Timecop.freeze` usage in specs.
#
# @example
#
# # bad
# Timecop.freeze(Time.current) { example.run }
#
# # good
# freeze_time(Time.current) { example.run }
#
class TimecopFreeze < RuboCop::Cop::Base
extend RuboCop::Cop::AutoCorrector
include MatchRange
MESSAGE = 'Do not use `Timecop.freeze`, use `freeze_time` instead. ' \
'See https://gitlab.com/gitlab-org/gitlab/-/issues/214432 for more info.'
def_node_matcher :timecop_freeze?, <<~PATTERN
(send (const nil? :Timecop) :freeze ?_)
PATTERN
def on_send(node)
return unless timecop_freeze?(node)
add_offense(node, message: MESSAGE) do |corrector|
each_match_range(node.source_range, /^(Timecop\.freeze)/) do |match_range|
corrector.replace(match_range, 'freeze_time')
end
end
end
end
end
end
end

View File

@ -1,41 +0,0 @@
# frozen_string_literal: true
require 'rubocop-rspec'
module RuboCop
module Cop
module RSpec
# This cop checks for `Timecop.travel` usage in specs.
#
# @example
#
# # bad
# Timecop.travel(1.day.ago) { create(:issue) }
#
# # good
# travel_to(1.day.ago) { create(:issue) }
#
class TimecopTravel < RuboCop::Cop::Base
extend RuboCop::Cop::AutoCorrector
include MatchRange
MESSAGE = 'Do not use `Timecop.travel`, use `travel_to` instead. ' \
'See https://gitlab.com/gitlab-org/gitlab/-/issues/214432 for more info.'
def_node_matcher :timecop_travel?, <<~PATTERN
(send (const nil? :Timecop) :travel _)
PATTERN
def on_send(node)
return unless timecop_travel?(node)
add_offense(node, message: MESSAGE) do |corrector|
each_match_range(node.source_range, /^(Timecop\.travel)/) do |match_range|
corrector.replace(match_range, 'travel_to')
end
end
end
end
end
end
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Dashboard snippets', feature_category: :snippets do
RSpec.describe 'Dashboard snippets', feature_category: :source_code_management do
let_it_be(:user) { create(:user) }
context 'when the project has snippets' do

View File

@ -33,6 +33,10 @@ RSpec.describe 'Projects > Settings > Packages', :js do
it 'displays the packages access level setting' do
expect(page).to have_selector('[data-testid="package-registry-access-level"] > label', text: 'Package registry')
expect(page).to have_selector('input[name="package_registry_enabled"]', visible: false)
expect(page).to have_selector('input[name="package_registry_enabled"] + button', visible: true)
expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"]', visible: false)
expect(page).to have_selector('input[name="package_registry_api_for_everyone_enabled"] + button', visible: true)
expect(page).to have_selector(
'input[name="project[project_feature_attributes][package_registry_access_level]"]',
visible: false

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Embedded Snippets', feature_category: :snippets do
RSpec.describe 'Embedded Snippets', feature_category: :source_code_management do
let_it_be(:snippet) { create(:personal_snippet, :public, :repository) }
let(:blobs) { snippet.blobs.first(3) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Explore Snippets', feature_category: :snippets do
RSpec.describe 'Explore Snippets', feature_category: :source_code_management do
let!(:public_snippet) { create(:personal_snippet, :public) }
let!(:internal_snippet) { create(:personal_snippet, :internal) }
let!(:private_snippet) { create(:personal_snippet, :private) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Internal Snippets', :js, feature_category: :snippets do
RSpec.describe 'Internal Snippets', :js, feature_category: :source_code_management do
let(:internal_snippet) { create(:personal_snippet, :internal, :repository) }
let(:content) { internal_snippet.blobs.first.data.strip! }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Comments on personal snippets', :js, feature_category: :snippets do
RSpec.describe 'Comments on personal snippets', :js, feature_category: :source_code_management do
include NoteInteractionHelpers
include Spec::Support::Helpers::ModalHelpers

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Private Snippets', :js, feature_category: :snippets do
RSpec.describe 'Private Snippets', :js, feature_category: :source_code_management do
let(:user) { create(:user) }
let(:private_snippet) { create(:personal_snippet, :repository, :private, author: user) }
let(:content) { private_snippet.blobs.first.data.strip! }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Public Snippets', :js, feature_category: :snippets do
RSpec.describe 'Public Snippets', :js, feature_category: :source_code_management do
let(:public_snippet) { create(:personal_snippet, :public, :repository) }
let(:content) { public_snippet.blobs.first.data.strip! }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Search Snippets', :js, feature_category: :snippets do
RSpec.describe 'Search Snippets', :js, feature_category: :source_code_management do
it 'user searches for snippets by title' do
public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle')
private_snippet = create(:personal_snippet, :private, title: 'Middle and End')

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'Snippet', :js, feature_category: :snippets do
RSpec.describe 'Snippet', :js, feature_category: :source_code_management do
let_it_be(:user) { create(:user) }
let_it_be(:snippet) { create(:personal_snippet, :public, :repository, author: user) }

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe 'snippet editor with spam', skip: "Will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/217722",
feature_category: :snippets do
feature_category: :source_code_management do
include_context 'includes Spam constants'
let_it_be(:user) { create(:user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'User creates snippet', :js, feature_category: :snippets do
RSpec.describe 'User creates snippet', :js, feature_category: :source_code_management do
include DropzoneHelper
include Spec::Support::Helpers::Features::SnippetSpecHelpers

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'User deletes snippet', :js, feature_category: :snippets do
RSpec.describe 'User deletes snippet', :js, feature_category: :source_code_management do
let(:user) { create(:user) }
let(:content) { 'puts "test"' }
let(:snippet) { create(:personal_snippet, :repository, :public, content: content, author: user) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'User edits snippet', :js, feature_category: :snippets do
RSpec.describe 'User edits snippet', :js, feature_category: :source_code_management do
include DropzoneHelper
include Spec::Support::Helpers::Features::SnippetSpecHelpers

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'User Snippets', feature_category: :snippets do
RSpec.describe 'User Snippets', feature_category: :source_code_management do
let(:author) { create(:user) }
let!(:public_snippet) { create(:personal_snippet, :public, author: author, title: "This is a public snippet") }
let!(:internal_snippet) { create(:personal_snippet, :internal, author: author, title: "This is an internal snippet") }

View File

@ -114,9 +114,14 @@ describe('Settings Panel', () => {
const findPackageSettings = () => wrapper.findComponent({ ref: 'package-settings' });
const findPackageAccessLevel = () =>
wrapper.find('[data-testid="package-registry-access-level"]');
const findPackageAccessLevels = () =>
wrapper.find('[name="project[project_feature_attributes][package_registry_access_level]"]');
const findPackagesEnabledInput = () => wrapper.find('[name="project[packages_enabled]"]');
const findPackageRegistryEnabledInput = () => wrapper.find('[name="package_registry_enabled"]');
const findPackageRegistryAccessLevelHiddenInput = () =>
wrapper.find(
'input[name="project[project_feature_attributes][package_registry_access_level]"]',
);
const findPackageRegistryApiForEveryoneEnabledInput = () =>
wrapper.find('[name="package_registry_api_for_everyone_enabled"]');
const findPagesSettings = () => wrapper.findComponent({ ref: 'pages-settings' });
const findPagesAccessLevels = () =>
wrapper.find('[name="project[project_feature_attributes][pages_access_level]"]');
@ -587,28 +592,63 @@ describe('Settings Panel', () => {
expect(findPackageAccessLevel().exists()).toBe(true);
});
it('has hidden input field for package registry access level', () => {
wrapper = mountComponent({
glFeatures: { packageRegistryAccessLevel: true },
packagesAvailable: true,
});
expect(findPackageRegistryAccessLevelHiddenInput().exists()).toBe(true);
});
it.each`
visibilityLevel | output
${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${[[featureAccessLevel.PROJECT_MEMBERS, 'Only Project Members'], [30, 'Everyone']]}
${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${[[featureAccessLevel.EVERYONE, 'Everyone With Access'], [30, 'Everyone']]}
${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${[[30, 'Everyone']]}
projectVisibilityLevel | packageRegistryEnabled | packageRegistryApiForEveryoneEnabled | expectedAccessLevel
${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED}
${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${false} | ${featureAccessLevel.PROJECT_MEMBERS}
${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${false} | ${'disabled'} | ${featureAccessLevel.NOT_ENABLED}
${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${false} | ${featureAccessLevel.EVERYONE}
${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${true} | ${true} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${false} | ${'hidden'} | ${featureAccessLevel.NOT_ENABLED}
${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${true} | ${'hidden'} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
`(
'renders correct options when visibilityLevel is $visibilityLevel',
async ({ visibilityLevel, output }) => {
'sets correct access level',
async ({
projectVisibilityLevel,
packageRegistryEnabled,
packageRegistryApiForEveryoneEnabled,
expectedAccessLevel,
}) => {
wrapper = mountComponent({
glFeatures: { packageRegistryAccessLevel: true },
packagesAvailable: true,
currentSettings: {
visibilityLevel,
visibilityLevel: projectVisibilityLevel,
},
});
expect(findPackageAccessLevels().props('options')).toStrictEqual(output);
await findPackageRegistryEnabledInput().vm.$emit('change', packageRegistryEnabled);
const packageRegistryApiForEveryoneEnabledInput = findPackageRegistryApiForEveryoneEnabledInput();
if (packageRegistryApiForEveryoneEnabled === 'hidden') {
expect(packageRegistryApiForEveryoneEnabledInput.exists()).toBe(false);
} else if (packageRegistryApiForEveryoneEnabled === 'disabled') {
expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(true);
} else {
expect(packageRegistryApiForEveryoneEnabledInput.props('disabled')).toBe(false);
await packageRegistryApiForEveryoneEnabledInput.vm.$emit(
'change',
packageRegistryApiForEveryoneEnabled,
);
}
expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel);
},
);
it.each`
initialProjectVisibilityLevel | newProjectVisibilityLevel | initialPackageRegistryOption | expectedPackageRegistryOption
initialProjectVisibilityLevel | newProjectVisibilityLevel | initialAccessLevel | expectedAccessLevel
${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.PROJECT_MEMBERS} | ${featureAccessLevel.EVERYONE}
${VISIBILITY_LEVEL_PRIVATE_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS}
@ -626,27 +666,25 @@ describe('Settings Panel', () => {
${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${featureAccessLevel.NOT_ENABLED} | ${featureAccessLevel.NOT_ENABLED}
${VISIBILITY_LEVEL_PUBLIC_INTEGER} | ${VISIBILITY_LEVEL_INTERNAL_INTEGER} | ${FEATURE_ACCESS_LEVEL_ANONYMOUS} | ${featureAccessLevel.EVERYONE}
`(
'changes option from $initialPackageRegistryOption to $expectedPackageRegistryOption when visibilityLevel changed from $initialProjectVisibilityLevel to $newProjectVisibilityLevel',
'changes access level when project visibility level changed',
async ({
initialProjectVisibilityLevel,
newProjectVisibilityLevel,
initialPackageRegistryOption,
expectedPackageRegistryOption,
initialAccessLevel,
expectedAccessLevel,
}) => {
wrapper = mountComponent({
glFeatures: { packageRegistryAccessLevel: true },
packagesAvailable: true,
currentSettings: {
visibilityLevel: initialProjectVisibilityLevel,
packageRegistryAccessLevel: initialPackageRegistryOption,
packageRegistryAccessLevel: initialAccessLevel,
},
});
await findProjectVisibilityLevelInput().setValue(newProjectVisibilityLevel);
expect(findPackageAccessLevels().props('value')).toStrictEqual(
expectedPackageRegistryOption,
);
expect(wrapper.vm.packageRegistryAccessLevel).toBe(expectedAccessLevel);
},
);
});

View File

@ -32,10 +32,21 @@ RSpec.describe 'Every API endpoint' do
next unless used_category
next if used_category == :not_owned
[path, used_category] unless feature_categories.include?(used_category)
[klass, path, used_category] unless feature_categories.include?(used_category)
end.compact
expect(routes_unknown_category).to be_empty, "#{routes_unknown_category.first(10)} had an unknown category"
message = -> do
list = routes_unknown_category.map do |klass, path, category|
"- #{klass} (#{path}): #{category}"
end
<<~MESSAGE
Unknown categories found for:
#{list.join("\n")}
MESSAGE
end
expect(routes_unknown_category).to be_empty, message
end
# This is required for API::Base.path_for_app to work, as it picks

View File

@ -20,10 +20,10 @@ RSpec.describe Gitlab::Memory::Watchdog::Configuration do
end
end
describe '#logger' do
context 'when logger is not set, defaults to stdout logger' do
it 'defaults to Logger' do
expect(configuration.logger).to be_an_instance_of(::Gitlab::Logger)
describe '#event_reporter' do
context 'when event reporter is not set' do
it 'defaults to EventReporter' do
expect(configuration.event_reporter).to be_an_instance_of(::Gitlab::Memory::Watchdog::EventReporter)
end
end
end

View File

@ -6,17 +6,23 @@ require 'sidekiq'
require_dependency 'gitlab/cluster/lifecycle_events'
RSpec.describe Gitlab::Memory::Watchdog::Configurator do
shared_examples 'as configurator' do |handler_class, sleep_time_env, sleep_time|
shared_examples 'as configurator' do |handler_class, event_reporter_class, sleep_time_env, sleep_time|
it 'configures the correct handler' do
configurator.call(configuration)
expect(configuration.handler).to be_an_instance_of(handler_class)
end
it 'configures the correct event reporter' do
configurator.call(configuration)
expect(configuration.event_reporter).to be_an_instance_of(event_reporter_class)
end
it 'configures the correct logger' do
configurator.call(configuration)
expect(configuration.logger).to eq(logger)
expect(configuration.event_reporter.logger).to eq(logger)
end
it 'does not enable writing heap dumps by default' do
@ -129,6 +135,7 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do
it_behaves_like 'as configurator',
Gitlab::Memory::Watchdog::PumaHandler,
Gitlab::Memory::Watchdog::EventReporter,
'GITLAB_MEMWD_SLEEP_TIME_SEC',
described_class::DEFAULT_SLEEP_INTERVAL_S
@ -236,6 +243,7 @@ RSpec.describe Gitlab::Memory::Watchdog::Configurator do
it_behaves_like 'as configurator',
Gitlab::Memory::Watchdog::TermProcessHandler,
Gitlab::Memory::Watchdog::EventReporter,
'SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL',
described_class::DEFAULT_SIDEKIQ_SLEEP_INTERVAL_S

View File

@ -0,0 +1,118 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'prometheus/client'
RSpec.describe Gitlab::Memory::Watchdog::EventReporter, feature_category: :application_performance do
let(:logger) { instance_double(::Logger) }
let(:violations_counter) { instance_double(::Prometheus::Client::Counter) }
let(:violations_handled_counter) { instance_double(::Prometheus::Client::Counter) }
let(:reporter) { described_class.new(logger: logger) }
def stub_prometheus_metrics
allow(Gitlab::Metrics).to receive(:counter)
.with(:gitlab_memwd_violations_total, anything, anything)
.and_return(violations_counter)
allow(Gitlab::Metrics).to receive(:counter)
.with(:gitlab_memwd_violations_handled_total, anything, anything)
.and_return(violations_handled_counter)
allow(violations_counter).to receive(:increment)
allow(violations_handled_counter).to receive(:increment)
end
before do
stub_prometheus_metrics
allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).at_least(:once).and_return(
total: 1024
)
allow(::Prometheus::PidProvider).to receive(:worker_id).and_return('worker_1')
end
describe '#logger' do
context 'when logger is not provided' do
let(:reporter) { described_class.new }
it 'uses default Gitlab::AppLogger' do
expect(reporter.logger).to eq(Gitlab::AppLogger)
end
end
end
describe '#started' do
it 'logs start message once' do
expect(logger).to receive(:info).once
.with(
pid: Process.pid,
worker_id: 'worker_1',
custom_label: 'dummy_label',
memwd_rss_bytes: 1024,
message: 'started')
reporter.started(custom_label: 'dummy_label')
end
end
describe '#stopped' do
subject { reporter.stopped(custom_label: 'dummy_label') }
it 'logs stop message once' do
expect(logger).to receive(:info).once
.with(
pid: Process.pid,
worker_id: 'worker_1',
custom_label: 'dummy_label',
memwd_rss_bytes: 1024,
message: 'stopped')
reporter.stopped(custom_label: 'dummy_label')
end
end
describe '#threshold_violated' do
subject { reporter.threshold_violated(:monitor_name) }
it 'increments violations counter' do
expect(violations_counter).to receive(:increment).with(reason: :monitor_name)
subject
end
it 'does not increment handled violations counter' do
expect(violations_handled_counter).not_to receive(:increment)
subject
end
it 'does not log violation' do
expect(logger).not_to receive(:warn)
subject
end
end
describe '#strikes_exceeded' do
subject { reporter.strikes_exceeded(:monitor_name, { message: 'dummy_text' }) }
before do
allow(logger).to receive(:warn)
end
it 'increments handled violations counter' do
expect(violations_handled_counter).to receive(:increment).with(reason: :monitor_name)
subject
end
it 'logs violation' do
expect(logger).to receive(:warn)
.with(
pid: Process.pid,
worker_id: 'worker_1',
memwd_rss_bytes: 1024,
message: 'dummy_text')
subject
end
end
end

View File

@ -6,12 +6,10 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
context 'watchdog' do
let(:configuration) { instance_double(described_class::Configuration) }
let(:handler) { instance_double(described_class::NullHandler) }
let(:logger) { instance_double(::Logger) }
let(:reporter) { instance_double(described_class::EventReporter) }
let(:sleep_time_seconds) { 60 }
let(:write_heap_dumps) { false }
let(:threshold_violated) { false }
let(:violations_counter) { instance_double(::Prometheus::Client::Counter) }
let(:violations_handled_counter) { instance_double(::Prometheus::Client::Counter) }
let(:watchdog_iterations) { 1 }
let(:name) { :monitor_name }
let(:payload) { { message: 'dummy_text' } }
@ -38,18 +36,6 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
end
end
def stub_prometheus_metrics
allow(Gitlab::Metrics).to receive(:counter)
.with(:gitlab_memwd_violations_total, anything, anything)
.and_return(violations_counter)
allow(Gitlab::Metrics).to receive(:counter)
.with(:gitlab_memwd_violations_handled_total, anything, anything)
.and_return(violations_handled_counter)
allow(violations_counter).to receive(:increment)
allow(violations_handled_counter).to receive(:increment)
end
describe '#initialize' do
it 'initialize new configuration' do
expect(described_class::Configuration).to receive(:new)
@ -60,34 +46,27 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
describe '#call' do
before do
stub_prometheus_metrics
allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).at_least(:once).and_return(
total: 1024
)
allow(::Prometheus::PidProvider).to receive(:worker_id).and_return('worker_1')
watchdog.configure do |config|
config.handler = handler
config.logger = logger
config.event_reporter = reporter
config.sleep_time_seconds = sleep_time_seconds
config.write_heap_dumps = write_heap_dumps
config.monitors.push monitor_class, threshold_violated, payload, max_strikes: max_strikes
end
allow(handler).to receive(:call).and_return(true)
allow(logger).to receive(:info)
allow(logger).to receive(:warn)
allow(reporter).to receive(:started)
allow(reporter).to receive(:stopped)
allow(reporter).to receive(:threshold_violated)
allow(reporter).to receive(:strikes_exceeded)
end
it 'logs start message once' do
expect(logger).to receive(:info).once
it 'reports started event once' do
expect(reporter).to receive(:started).once
.with(
pid: Process.pid,
worker_id: 'worker_1',
memwd_handler_class: handler.class.name,
memwd_sleep_time_s: sleep_time_seconds,
memwd_rss_bytes: 1024,
message: 'started')
memwd_sleep_time_s: sleep_time_seconds
)
watchdog.call
end
@ -109,15 +88,9 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
end
context 'when process does not exceed threshold' do
it 'does not increment violations counters' do
expect(violations_counter).not_to receive(:increment)
expect(violations_handled_counter).not_to receive(:increment)
watchdog.call
end
it 'does not log violation' do
expect(logger).not_to receive(:warn)
it 'does not report violations event' do
expect(reporter).not_to receive(:threshold_violated)
expect(reporter).not_to receive(:strikes_exceeded)
watchdog.call
end
@ -132,21 +105,15 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
context 'when process exceeds threshold' do
let(:threshold_violated) { true }
it 'increments violations counter' do
expect(violations_counter).to receive(:increment).with(reason: name)
it 'reports threshold violated event' do
expect(reporter).to receive(:threshold_violated).with(name)
watchdog.call
end
context 'when process does not exceed the allowed number of strikes' do
it 'does not increment handled violations counter' do
expect(violations_handled_counter).not_to receive(:increment)
watchdog.call
end
it 'does not log violation' do
expect(logger).not_to receive(:warn)
it 'does not report strikes exceeded event' do
expect(reporter).not_to receive(:strikes_exceeded)
watchdog.call
end
@ -171,23 +138,16 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
context 'when monitor exceeds the allowed number of strikes' do
let(:max_strikes) { 0 }
it 'increments handled violations counter' do
expect(violations_handled_counter).to receive(:increment).with(reason: name)
watchdog.call
end
it 'logs violation' do
expect(logger).to receive(:warn)
it 'reports strikes exceeded event' do
expect(reporter).to receive(:strikes_exceeded)
.with(
pid: Process.pid,
worker_id: 'worker_1',
name,
memwd_handler_class: handler.class.name,
memwd_sleep_time_s: sleep_time_seconds,
memwd_rss_bytes: 1024,
memwd_cur_strikes: 1,
memwd_max_strikes: max_strikes,
message: 'dummy_text')
message: "dummy_text"
)
watchdog.call
end
@ -225,7 +185,7 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
before do
watchdog.configure do |config|
config.handler = handler
config.logger = logger
config.event_reporter = reporter
config.sleep_time_seconds = sleep_time_seconds
config.monitors.push monitor_class, threshold_violated, payload, max_strikes: max_strikes
config.monitors.push monitor_class, threshold_violated, payload, max_strikes: max_strikes
@ -241,15 +201,12 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
end
end
it 'logs stop message once' do
expect(logger).to receive(:info).once
it 'reports stopped event once' do
expect(reporter).to receive(:stopped).once
.with(
pid: Process.pid,
worker_id: 'worker_1',
memwd_handler_class: handler.class.name,
memwd_sleep_time_s: sleep_time_seconds,
memwd_rss_bytes: 1024,
message: 'stopped')
memwd_sleep_time_s: sleep_time_seconds
)
watchdog.call
end

View File

@ -3,7 +3,9 @@
require 'spec_helper'
RSpec.describe UserPreference do
let(:user_preference) { create(:user_preference) }
let_it_be(:user) { create(:user) }
let(:user_preference) { create(:user_preference, user: user) }
describe 'validations' do
describe 'diffs_deletion_color and diffs_addition_color' do
@ -132,10 +134,24 @@ RSpec.describe UserPreference do
describe '#tab_width' do
it 'is set to 8 by default' do
# Intentionally not using factory here to test the constructor.
pref = UserPreference.new
pref = described_class.new
expect(pref.tab_width).to eq(8)
end
it 'returns default value when assigning nil' do
pref = described_class.new(tab_width: nil)
expect(pref.tab_width).to eq(8)
end
it 'returns default value when the value is NULL' do
pref = create(:user_preference, user: user)
pref.update_column(:tab_width, nil)
expect(pref.reload.tab_width).to eq(8)
end
it do
is_expected.to validate_numericality_of(:tab_width)
.only_integer
@ -143,4 +159,141 @@ RSpec.describe UserPreference do
.is_less_than_or_equal_to(12)
end
end
describe '#tab_width=' do
it 'sets to default value when nil' do
pref = described_class.new(tab_width: nil)
expect(pref.read_attribute(:tab_width)).to eq(8)
end
it 'sets user values' do
pref = described_class.new(tab_width: 12)
expect(pref.read_attribute(:tab_width)).to eq(12)
end
end
describe '#time_display_relative' do
it 'is set to true by default' do
pref = described_class.new
expect(pref.time_display_relative).to eq(true)
end
it 'returns default value when assigning nil' do
pref = described_class.new(time_display_relative: nil)
expect(pref.time_display_relative).to eq(true)
end
it 'returns default value when the value is NULL' do
pref = create(:user_preference, user: user)
pref.update_column(:time_display_relative, nil)
expect(pref.reload.time_display_relative).to eq(true)
end
it 'returns assigned value' do
pref = described_class.new(time_display_relative: false)
expect(pref.time_display_relative).to eq(false)
end
end
describe '#time_display_relative=' do
it 'sets to default value when nil' do
pref = described_class.new(time_display_relative: nil)
expect(pref.read_attribute(:time_display_relative)).to eq(true)
end
it 'sets user values' do
pref = described_class.new(time_display_relative: false)
expect(pref.read_attribute(:time_display_relative)).to eq(false)
end
end
describe '#time_format_in_24h' do
it 'is set to false by default' do
pref = described_class.new
expect(pref.time_format_in_24h).to eq(false)
end
it 'returns default value when assigning nil' do
pref = described_class.new(time_format_in_24h: nil)
expect(pref.time_format_in_24h).to eq(false)
end
it 'returns default value when the value is NULL' do
pref = create(:user_preference, user: user)
pref.update_column(:time_format_in_24h, nil)
expect(pref.reload.time_format_in_24h).to eq(false)
end
it 'returns assigned value' do
pref = described_class.new(time_format_in_24h: true)
expect(pref.time_format_in_24h).to eq(true)
end
end
describe '#time_format_in_24h=' do
it 'sets to default value when nil' do
pref = described_class.new(time_format_in_24h: nil)
expect(pref.read_attribute(:time_format_in_24h)).to eq(false)
end
it 'sets user values' do
pref = described_class.new(time_format_in_24h: true)
expect(pref.read_attribute(:time_format_in_24h)).to eq(true)
end
end
describe '#render_whitespace_in_code' do
it 'is set to false by default' do
pref = described_class.new
expect(pref.render_whitespace_in_code).to eq(false)
end
it 'returns default value when assigning nil' do
pref = described_class.new(render_whitespace_in_code: nil)
expect(pref.render_whitespace_in_code).to eq(false)
end
it 'returns default value when the value is NULL' do
pref = create(:user_preference, user: user)
pref.update_column(:render_whitespace_in_code, nil)
expect(pref.reload.render_whitespace_in_code).to eq(false)
end
it 'returns assigned value' do
pref = described_class.new(render_whitespace_in_code: true)
expect(pref.render_whitespace_in_code).to eq(true)
end
end
describe '#render_whitespace_in_code=' do
it 'sets to default value when nil' do
pref = described_class.new(render_whitespace_in_code: nil)
expect(pref.read_attribute(:render_whitespace_in_code)).to eq(false)
end
it 'sets user values' do
pref = described_class.new(render_whitespace_in_code: true)
expect(pref.read_attribute(:render_whitespace_in_code)).to eq(true)
end
end
end

View File

@ -146,6 +146,21 @@ RSpec.describe User do
it { is_expected.to have_many(:project_callouts).class_name('Users::ProjectCallout') }
it { is_expected.to have_many(:created_projects).dependent(:nullify).class_name('Project') }
describe 'default values' do
let(:user) { described_class.new }
it { expect(user.admin).to be_falsey }
it { expect(user.external).to eq(Gitlab::CurrentSettings.user_default_external) }
it { expect(user.can_create_group).to eq(Gitlab::CurrentSettings.can_create_group) }
it { expect(user.can_create_team).to be_falsey }
it { expect(user.hide_no_ssh_key).to be_falsey }
it { expect(user.hide_no_password).to be_falsey }
it { expect(user.project_view).to eq('files') }
it { expect(user.notified_of_own_activity).to be_falsey }
it { expect(user.preferred_language).to eq(I18n.default_locale.to_s) }
it { expect(user.theme_id).to eq(described_class.gitlab_config.default_theme) }
end
describe '#user_detail' do
it 'does not persist `user_detail` by default' do
expect(create(:user).user_detail).not_to be_persisted
@ -398,7 +413,7 @@ RSpec.describe User do
end
it 'falls back to english when I18n.default_locale is not an available language' do
I18n.default_locale = :kl
allow(I18n).to receive(:default_locale) { :kl }
default_preferred_language = user.send(:default_preferred_language)
expect(user.preferred_language).to eq default_preferred_language
@ -7307,4 +7322,51 @@ RSpec.describe User do
expect(user.account_age_in_days).to be(1)
end
end
describe 'state machine and default attributes' do
let(:model) do
Class.new(ApplicationRecord) do
self.table_name = User.table_name
attribute :external, default: -> { 1 / 0 }
state_machine :state, initial: :active do
end
end
end
it 'raises errors by default' do
expect { model }.to raise_error(ZeroDivisionError)
end
context 'with state machine default attributes override' do
let(:model) do
Class.new(ApplicationRecord) do
self.table_name = User.table_name
attribute :external, default: -> { 1 / 0 }
state_machine :state, initial: :active do
def owner_class_attribute_default; end
end
end
end
it 'does not raise errors' do
expect { model }.not_to raise_error
end
it 'raises errors when default attributes are used' do
expect { model.new.attributes }.to raise_error(ZeroDivisionError)
end
it 'does not evaluate default attributes when values are provided' do
expect { model.new(external: false).attributes }.not_to raise_error
end
it 'sets the state machine default value' do
expect(model.new(external: true).state).to eq('active')
end
end
end
end

View File

@ -1,28 +0,0 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/rspec/timecop_freeze'
RSpec.describe RuboCop::Cop::RSpec::TimecopFreeze do
context 'when calling Timecop.freeze' do
it 'registers an offense and corrects', :aggregate_failures do
expect_offense(<<~CODE)
Timecop.freeze(Time.current) { example.run }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use `Timecop.freeze`, use `freeze_time` instead. [...]
CODE
expect_correction(<<~CODE)
freeze_time(Time.current) { example.run }
CODE
end
end
context 'when calling a different method on Timecop' do
it 'does not register an offense' do
expect_no_offenses(<<~CODE)
Timecop.travel(Time.current)
CODE
end
end
end

View File

@ -1,28 +0,0 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/rspec/timecop_travel'
RSpec.describe RuboCop::Cop::RSpec::TimecopTravel do
context 'when calling Timecop.travel' do
it 'registers an offense and corrects', :aggregate_failures do
expect_offense(<<~CODE)
Timecop.travel(1.day.ago) { create(:issue) }
^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use `Timecop.travel`, use `travel_to` instead. [...]
CODE
expect_correction(<<~CODE)
travel_to(1.day.ago) { create(:issue) }
CODE
end
end
context 'when calling a different method on Timecop' do
it 'does not register an offense' do
expect_no_offenses(<<~CODE)
Timecop.freeze { create(:issue) }
CODE
end
end
end

View File

@ -40,6 +40,14 @@ RSpec.describe ChatNames::FindUserService, :clean_gitlab_redis_shared_state do
expect(chat_name.reload.last_used_at).to eq(time)
end
context 'when integration is not passed' do
it 'returns chat name' do
requested_chat_name = described_class.new(nil, params).execute
expect(requested_chat_name).to eq(chat_name)
end
end
end
context 'when different user is requested' do

View File

@ -10,7 +10,7 @@ require (
github.com/aws/aws-sdk-go v1.44.145
github.com/disintegration/imaging v1.6.2
github.com/getsentry/raven-go v0.2.0
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f
github.com/golang/protobuf v1.5.2
github.com/gomodule/redigo v2.0.0+incompatible

View File

@ -686,8 +686,9 @@ github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfE
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=

View File

@ -1120,10 +1120,10 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.2.0"
"@gitlab/svgs@3.11.0":
version "3.11.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.11.0.tgz#91e8e25583cddef48c0c79175203e5b0a4eaa519"
integrity sha512-1cJu1WXPoOHfGgv5fT3nmA9cgAQ3U1Fm/oMSVYUgBxU35R0I8W704GMLsIZwBuQ/S/Ow7WLwIkoOhLb/spNKPg==
"@gitlab/svgs@3.12.0":
version "3.12.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.12.0.tgz#cebd2ebf21e803d0d9e674d43fcb2d868f5d5a62"
integrity sha512-7GDMXuBoOL380sjdBSpPufUzwd5dJgkzqgpx26JBlrO2ShSW0k5KnmIurSXSe8gpqwAEIEw/BbpjCz0SRRRwQg==
"@gitlab/ui@50.1.2":
version "50.1.2"