Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
20f6a17ba2
commit
6bed1b9c9c
3
Gemfile
3
Gemfile
|
|
@ -146,7 +146,7 @@ gem 'fog-aws', '~> 3.15'
|
|||
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
|
||||
# Also see config/initializers/fog_core_patch.rb.
|
||||
gem 'fog-core', '= 2.1.0'
|
||||
gem 'fog-google', '~> 1.15', require: 'fog/google'
|
||||
gem 'fog-google', '~> 1.19', require: 'fog/google'
|
||||
gem 'fog-local', '~> 0.8'
|
||||
gem 'fog-openstack', '~> 1.0'
|
||||
gem 'fog-rackspace', '~> 0.1.1'
|
||||
|
|
@ -551,6 +551,7 @@ gem 'valid_email', '~> 0.1'
|
|||
gem 'json', '~> 2.5.1'
|
||||
gem 'json_schemer', '~> 0.2.18'
|
||||
gem 'oj', '~> 3.13.21'
|
||||
gem 'oj-introspect', '~> 0.7'
|
||||
gem 'multi_json', '~> 1.14.1'
|
||||
gem 'yajl-ruby', '~> 1.4.3', require: 'yajl'
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@
|
|||
{"name":"fog-aliyun","version":"0.3.3","platform":"ruby","checksum":"d0aa317f7c1473a1d684fff51699f216bb9cb78b9ee9ce55a81c9bcc93fb85ee"},
|
||||
{"name":"fog-aws","version":"3.15.0","platform":"ruby","checksum":"09752931ea0c6165b018e1a89253248d86b246645086ccf19bc44fabe3381e8c"},
|
||||
{"name":"fog-core","version":"2.1.0","platform":"ruby","checksum":"53e5d793554d7080d015ef13cd44b54027e421d924d9dba4ce3d83f95f37eda9"},
|
||||
{"name":"fog-google","version":"1.15.0","platform":"ruby","checksum":"2f840780fbf2384718e961b05ef2fc522b4213bbda6f25b28c1bbd875ff0b306"},
|
||||
{"name":"fog-google","version":"1.19.0","platform":"ruby","checksum":"3c909a230837fe84117fffdfd927b523821b88f61d3aeab531e1417a9810f488"},
|
||||
{"name":"fog-json","version":"1.2.0","platform":"ruby","checksum":"dd4f5ab362dbc72b687240bba9d2dd841d5dfe888a285797533f85c03ea548fe"},
|
||||
{"name":"fog-local","version":"0.8.0","platform":"ruby","checksum":"263b2d09e54c69d1b87ad7f235a1a1e53c8a674edcedf7512c1715765ad7ef79"},
|
||||
{"name":"fog-openstack","version":"1.0.8","platform":"ruby","checksum":"8f174ab5e5b1bc107c7da90cc7c47a24930e1566cd88ab4df447026ea8b63d9c"},
|
||||
|
|
@ -193,6 +193,7 @@
|
|||
{"name":"fuubar","version":"2.2.0","platform":"ruby","checksum":"9b0263c4074f39c68b37f1e4e69a7d3cfc7523c41bea43601235daa723179b4a"},
|
||||
{"name":"fuzzyurl","version":"0.9.0","platform":"ruby","checksum":"542efa80f2bcaadbdc402c2f0b572f2e335a1d53e375aecad68bbb3d86860c0f"},
|
||||
{"name":"gemoji","version":"3.0.1","platform":"ruby","checksum":"80553f2f4932a7a95fb1b3c7c63f7dd937e7c8c610164bbdea28fd06eba5f36d"},
|
||||
{"name":"gems","version":"1.2.0","platform":"ruby","checksum":"343d74bd54d906f38193f3ccd983f9d08c4b54cd01ee7e5fe8467ab41a9946f0"},
|
||||
{"name":"get_process_mem","version":"0.2.7","platform":"ruby","checksum":"4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba"},
|
||||
{"name":"gettext","version":"3.3.6","platform":"ruby","checksum":"ee6bbd1b2f833ee52d7797fa68acbfecc4726aec6b6280fd7eab92aa0190b413"},
|
||||
{"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"},
|
||||
|
|
@ -216,7 +217,17 @@
|
|||
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
|
||||
{"name":"globalid","version":"1.0.0","platform":"ruby","checksum":"1253641b1dc3392721c964351773755d75135d3d3c5cc65d88b0a3880a60bed8"},
|
||||
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
|
||||
{"name":"google-api-client","version":"0.50.0","platform":"ruby","checksum":"3ae45e972f293f3a66e53950ecc0fd350d85d6347c06a430bb971bd1ae5ad617"},
|
||||
{"name":"google-api-client","version":"0.53.0","platform":"ruby","checksum":"41006ef21fe02a70cff39a10aebf84fa7fb5f24c63566ab12b149ff1f1d9d7ff"},
|
||||
{"name":"google-apis-compute_v1","version":"0.53.0","platform":"ruby","checksum":"629537cf9efc1aeda0bb00d78c2a6ffa8488de833a8b19bdb150ce0a6a105f4b"},
|
||||
{"name":"google-apis-core","version":"0.9.1","platform":"ruby","checksum":"c012a364891a4602b4b1aa8468400dd3fa50b00e694edb4411af6b85aa3eb034"},
|
||||
{"name":"google-apis-discovery_v1","version":"0.12.0","platform":"ruby","checksum":"2e5accfe126884e5ebd8540b3a17a878a3a050d0dfdf0ece6b231846fc485a15"},
|
||||
{"name":"google-apis-dns_v1","version":"0.28.0","platform":"ruby","checksum":"f523631ea2737b67096e21eff25e426edb51ffefa9979a42f798936a950df34c"},
|
||||
{"name":"google-apis-generator","version":"0.11.0","platform":"ruby","checksum":"4656febed121b21e9071118c79ab67cbec9e40a39b6a38acc05d07fafa321279"},
|
||||
{"name":"google-apis-iamcredentials_v1","version":"0.15.0","platform":"ruby","checksum":"e9a256a6d80fbfc77d44bd7e65bc94b9e1e9863a00e6d413edc0102d6cb5551b"},
|
||||
{"name":"google-apis-monitoring_v3","version":"0.37.0","platform":"ruby","checksum":"2d9262ae8dfa83ac7db895b03c7deeaae9f13107e94c8781a432202fbc20736a"},
|
||||
{"name":"google-apis-pubsub_v1","version":"0.30.0","platform":"ruby","checksum":"b8905915388041bf54f9b7e988c8cc64fe00c2132475d5c753d10479415ee13d"},
|
||||
{"name":"google-apis-sqladmin_v1beta4","version":"0.38.0","platform":"ruby","checksum":"d00279cdcc5548bf4f4e40cc29cbd942b79708011e59c75a18726b6826be1665"},
|
||||
{"name":"google-apis-storage_v1","version":"0.20.0","platform":"ruby","checksum":"8a1ace07fc909966d6f76e777d6adc7d86dddd91a629fef8914ebd5baf86d850"},
|
||||
{"name":"google-cloud-env","version":"1.6.0","platform":"ruby","checksum":"6179acb946975892c7908748df5722a4ebadfc8cf5bb7b0d8d933ca67183fa15"},
|
||||
{"name":"google-protobuf","version":"3.21.9","platform":"java","checksum":"8483ab2487170434f7a139d6534b3a166e4ec244a6fd8929f758d87abbb82fee"},
|
||||
{"name":"google-protobuf","version":"3.21.9","platform":"ruby","checksum":"5a656c159aa2c85008af7eab3f603cf22921b748e09438f6682dcf696d518adc"},
|
||||
|
|
@ -227,7 +238,7 @@
|
|||
{"name":"google-protobuf","version":"3.21.9","platform":"x86_64-darwin","checksum":"9e948a08ee27cca8acf794c798db16d918ce503eae06525d7551dc05ac3324c0"},
|
||||
{"name":"google-protobuf","version":"3.21.9","platform":"x86_64-linux","checksum":"d4053012022f7bf47cd54c7c19416f600325e6cc1e1604a631c2fde69dd920a4"},
|
||||
{"name":"googleapis-common-protos-types","version":"1.3.0","platform":"ruby","checksum":"c5411f3197cc3e02547ded1858303b1f830b4dc89c588c142ad6c8a231050671"},
|
||||
{"name":"googleauth","version":"0.14.0","platform":"ruby","checksum":"4659b563d5b2727e775ba9231e75485c1b55ac8fc319e0bf1bc87d5e9705a632"},
|
||||
{"name":"googleauth","version":"1.3.0","platform":"ruby","checksum":"51dd7362353cf1e90a2d01e1fb94321ae3926c776d4dc4a79db65230217ffcc2"},
|
||||
{"name":"gpgme","version":"2.0.20","platform":"ruby","checksum":"fc194689cff40cd4ccafb3086031e930650b3efc15348bbfdf7a2f8b5a826f75"},
|
||||
{"name":"grape","version":"1.5.2","platform":"ruby","checksum":"1df3b734c3862e235174232bc629587eddda9ef3df648230827575186700ae29"},
|
||||
{"name":"grape-entity","version":"0.10.0","platform":"ruby","checksum":"9aed1e7cbbc96d9e73f72e5f32c776d4ba8a5baf54c3acda2682008dba2b2cfe"},
|
||||
|
|
@ -372,7 +383,8 @@
|
|||
{"name":"oauth2","version":"2.0.9","platform":"ruby","checksum":"b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb"},
|
||||
{"name":"octokit","version":"4.25.1","platform":"ruby","checksum":"c02092ee82dcdfe84db0e0ea630a70d32becc54245a4f0bacfd21c010df09b96"},
|
||||
{"name":"ohai","version":"16.10.6","platform":"ruby","checksum":"b835806e585faea4ac8346b68c722fb5fc29a29f73fd7e3a022f9073132dec22"},
|
||||
{"name":"oj","version":"3.13.21","platform":"ruby","checksum":"aef31a8dcc6f0b9b4bb5cc7ac6cc5272b2d851deb11a1804c2ed6b5501b50e46"},
|
||||
{"name":"oj","version":"3.13.23","platform":"ruby","checksum":"206dfdc4020ad9974705037f269cfba211d61b7662a58c717cce771829ccef51"},
|
||||
{"name":"oj-introspect","version":"0.7.0","platform":"ruby","checksum":"dacd2504fedf67ed26733efa753e3b4da1888ad2a9752e81948acb8a196b8420"},
|
||||
{"name":"omniauth","version":"2.1.0","platform":"ruby","checksum":"bff7234f5ec9323622b217c7f26d52f850de0b0e2b8c807c3358fc79fe572300"},
|
||||
{"name":"omniauth-alicloud","version":"2.0.0","platform":"ruby","checksum":"8ecf369d51cd5317c1e7c6b80276891f76cff210a534ec654326af5c62265de3"},
|
||||
{"name":"omniauth-atlassian-oauth2","version":"0.2.0","platform":"ruby","checksum":"eb07574a188ab8a03376ce288bce86bc2dd4a1382ffa5781cb5e2b7bc15d76c9"},
|
||||
|
|
|
|||
61
Gemfile.lock
61
Gemfile.lock
|
|
@ -501,11 +501,17 @@ GEM
|
|||
excon (~> 0.58)
|
||||
formatador (~> 0.2)
|
||||
mime-types
|
||||
fog-google (1.15.0)
|
||||
fog-core (<= 2.1.0)
|
||||
fog-google (1.19.0)
|
||||
fog-core (< 2.3)
|
||||
fog-json (~> 1.2)
|
||||
fog-xml (~> 0.1.0)
|
||||
google-api-client (>= 0.44.2, < 0.51)
|
||||
google-apis-compute_v1 (~> 0.14)
|
||||
google-apis-dns_v1 (~> 0.12)
|
||||
google-apis-iamcredentials_v1 (~> 0.6)
|
||||
google-apis-monitoring_v3 (~> 0.12)
|
||||
google-apis-pubsub_v1 (~> 0.7)
|
||||
google-apis-sqladmin_v1beta4 (~> 0.13)
|
||||
google-apis-storage_v1 (~> 0.6)
|
||||
google-cloud-env (~> 1.2)
|
||||
fog-json (1.2.0)
|
||||
fog-core
|
||||
|
|
@ -533,6 +539,7 @@ GEM
|
|||
ruby-progressbar (~> 1.4)
|
||||
fuzzyurl (0.9.0)
|
||||
gemoji (3.0.1)
|
||||
gems (1.2.0)
|
||||
get_process_mem (0.2.7)
|
||||
ffi (~> 1.0)
|
||||
gettext (3.3.6)
|
||||
|
|
@ -608,27 +615,52 @@ GEM
|
|||
i18n (>= 0.7)
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
google-api-client (0.50.0)
|
||||
google-api-client (0.53.0)
|
||||
google-apis-core (~> 0.1)
|
||||
google-apis-generator (~> 0.1)
|
||||
google-apis-compute_v1 (0.53.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-core (0.9.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
signet (~> 0.12)
|
||||
webrick
|
||||
google-apis-discovery_v1 (0.12.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-dns_v1 (0.28.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-generator (0.11.0)
|
||||
activesupport (>= 5.0)
|
||||
gems (~> 1.2)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-discovery_v1 (~> 0.5)
|
||||
thor (>= 0.20, < 2.a)
|
||||
google-apis-iamcredentials_v1 (0.15.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-monitoring_v3 (0.37.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-pubsub_v1 (0.30.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-sqladmin_v1beta4 (0.38.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-storage_v1 (0.20.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-protobuf (3.21.9)
|
||||
googleapis-common-protos-types (1.3.0)
|
||||
google-protobuf (~> 3.14)
|
||||
googleauth (0.14.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
googleauth (1.3.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.14)
|
||||
signet (>= 0.16, < 2.a)
|
||||
gpgme (2.0.20)
|
||||
mini_portile2 (~> 2.3)
|
||||
grape (1.5.2)
|
||||
|
|
@ -937,7 +969,9 @@ GEM
|
|||
plist (~> 3.1)
|
||||
train-core
|
||||
wmi-lite (~> 1.0)
|
||||
oj (3.13.21)
|
||||
oj (3.13.23)
|
||||
oj-introspect (0.7.0)
|
||||
oj (>= 3.13.23)
|
||||
omniauth (2.1.0)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 2.2.3)
|
||||
|
|
@ -1616,7 +1650,7 @@ DEPENDENCIES
|
|||
fog-aliyun (~> 0.3)
|
||||
fog-aws (~> 3.15)
|
||||
fog-core (= 2.1.0)
|
||||
fog-google (~> 1.15)
|
||||
fog-google (~> 1.19)
|
||||
fog-local (~> 0.8)
|
||||
fog-openstack (~> 1.0)
|
||||
fog-rackspace (~> 0.1.1)
|
||||
|
|
@ -1705,6 +1739,7 @@ DEPENDENCIES
|
|||
octokit (~> 4.15)
|
||||
ohai (~> 16.10)
|
||||
oj (~> 3.13.21)
|
||||
oj-introspect (~> 0.7)
|
||||
omniauth (~> 2.1.0)
|
||||
omniauth-alicloud (~> 2.0.0)
|
||||
omniauth-atlassian-oauth2 (~> 0.2.0)
|
||||
|
|
|
|||
|
|
@ -129,9 +129,6 @@ export default {
|
|||
issuableId() {
|
||||
return this.issuable?.id;
|
||||
},
|
||||
isRealtimeEnabled() {
|
||||
return this.glFeatures.realtimeLabels;
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
issuable: {
|
||||
|
|
@ -163,7 +160,7 @@ export default {
|
|||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.issuableId || !this.isDropdownVariantSidebar || !this.isRealtimeEnabled;
|
||||
return !this.issuableId || !this.isDropdownVariantSidebar;
|
||||
},
|
||||
updateQuery(
|
||||
_,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { GlLink, GlIcon, GlLabel, GlFormCheckbox, GlSprintf, GlTooltipDirective
|
|||
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
import { differenceInSeconds, getTimeago, SECONDS_IN_DAY } from '~/lib/utils/datetime_utility';
|
||||
import { getTimeago } from '~/lib/utils/datetime_utility';
|
||||
import { isExternal, setUrlFragment } from '~/lib/utils/url_utility';
|
||||
import { __, n__, sprintf } from '~/locale';
|
||||
import IssuableAssignees from '~/issuable/components/issue_assignees.vue';
|
||||
|
|
@ -65,10 +65,6 @@ export default {
|
|||
issuableIid() {
|
||||
return this.issuable.iid;
|
||||
},
|
||||
createdInPastDay() {
|
||||
const createdSecondsAgo = differenceInSeconds(new Date(this.issuable.createdAt), new Date());
|
||||
return createdSecondsAgo < SECONDS_IN_DAY;
|
||||
},
|
||||
author() {
|
||||
return this.issuable.author || {};
|
||||
},
|
||||
|
|
@ -187,7 +183,7 @@ export default {
|
|||
<li
|
||||
:id="`issuable_${issuableId}`"
|
||||
class="issue gl-display-flex! gl-px-5!"
|
||||
:class="{ closed: issuable.closedAt, today: createdInPastDay }"
|
||||
:class="{ closed: issuable.closedAt }"
|
||||
:data-labels="labelIdsString"
|
||||
:data-qa-issue-id="issuableId"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_m
|
|||
import { __, s__ } from '~/locale';
|
||||
import EditedAt from '~/issues/show/components/edited.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
|
||||
import { getWorkItemQuery } from '../utils';
|
||||
import workItemDescriptionSubscription from '../graphql/work_item_description.subscription.graphql';
|
||||
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
|
||||
|
|
@ -19,10 +21,11 @@ export default {
|
|||
EditedAt,
|
||||
GlButton,
|
||||
GlFormGroup,
|
||||
MarkdownEditor,
|
||||
MarkdownField,
|
||||
WorkItemDescriptionRendered,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
mixins: [glFeatureFlagMixin(), Tracking.mixin()],
|
||||
props: {
|
||||
workItemId: {
|
||||
type: String,
|
||||
|
|
@ -133,7 +136,7 @@ export default {
|
|||
|
||||
await this.$nextTick();
|
||||
|
||||
this.$refs.textarea.focus();
|
||||
this.$refs.textarea?.focus();
|
||||
},
|
||||
async cancelEditing() {
|
||||
const isDirty = this.descriptionText !== this.workItemDescription?.description;
|
||||
|
|
@ -200,6 +203,10 @@ export default {
|
|||
|
||||
this.isSubmitting = false;
|
||||
},
|
||||
setDescriptionText(newText) {
|
||||
this.descriptionText = newText;
|
||||
updateDraft(this.autosaveKey, this.descriptionText);
|
||||
},
|
||||
handleDescriptionTextUpdated(newText) {
|
||||
this.descriptionText = newText;
|
||||
this.updateWorkItem();
|
||||
|
|
@ -216,7 +223,24 @@ export default {
|
|||
:label="__('Description')"
|
||||
label-for="work-item-description"
|
||||
>
|
||||
<markdown-editor
|
||||
v-if="glFeatures.workItemsMvc2"
|
||||
class="gl-my-3 common-note-form"
|
||||
:value="descriptionText"
|
||||
:render-markdown-path="markdownPreviewPath"
|
||||
:markdown-docs-path="$options.markdownDocsPath"
|
||||
:form-field-aria-label="__('Description')"
|
||||
:form-field-placeholder="__('Write a comment or drag your files here…')"
|
||||
form-field-id="work-item-description"
|
||||
form-field-name="work-item-description"
|
||||
enable-autocomplete
|
||||
init-on-autofocus
|
||||
@input="setDescriptionText"
|
||||
@keydown.meta.enter="updateWorkItem"
|
||||
@keydown.ctrl.enter="updateWorkItem"
|
||||
/>
|
||||
<markdown-field
|
||||
v-else
|
||||
can-attach-file
|
||||
:textarea-value="descriptionText"
|
||||
:is-submitting="isSubmitting"
|
||||
|
|
|
|||
|
|
@ -758,12 +758,6 @@ $input-lg-width: 320px;
|
|||
$document-index-color: #888;
|
||||
$help-shortcut-header-color: #333;
|
||||
|
||||
/*
|
||||
* Issues
|
||||
*/
|
||||
$issues-today-bg: #f3fff2 !default;
|
||||
$issues-today-border: #e1e8d5 !default;
|
||||
|
||||
/*
|
||||
* Label
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -75,11 +75,6 @@ ul.related-merge-requests > li gl-emoji {
|
|||
|
||||
.merge-request,
|
||||
.issue {
|
||||
&.today {
|
||||
background: $issues-today-bg;
|
||||
border-color: $issues-today-border;
|
||||
}
|
||||
|
||||
&.closed,
|
||||
&.merged {
|
||||
background: $gray-light;
|
||||
|
|
|
|||
|
|
@ -255,9 +255,6 @@ $popover-arrow-outer-color: $gray-800;
|
|||
|
||||
$secondary: $gray-600;
|
||||
|
||||
$issues-today-bg: #333838;
|
||||
$issues-today-border: #333a40;
|
||||
|
||||
$yiq-text-dark: $gray-50;
|
||||
$yiq-text-light: $gray-950;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ class Groups::BoardsController < Groups::ApplicationController
|
|||
before_action do
|
||||
push_frontend_feature_flag(:board_multi_select, group)
|
||||
push_frontend_feature_flag(:apollo_boards, group)
|
||||
push_frontend_feature_flag(:realtime_labels, group)
|
||||
experiment(:prominent_create_board_btn, subject: current_user) do |e|
|
||||
e.control {}
|
||||
e.candidate {}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ class Projects::BoardsController < Projects::ApplicationController
|
|||
before_action do
|
||||
push_frontend_feature_flag(:board_multi_select, project)
|
||||
push_frontend_feature_flag(:apollo_boards, project)
|
||||
push_frontend_feature_flag(:realtime_labels, project&.group)
|
||||
experiment(:prominent_create_board_btn, subject: current_user) do |e|
|
||||
e.control {}
|
||||
e.candidate {}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
before_action only: :show do
|
||||
push_frontend_feature_flag(:issue_assignees_widget, project)
|
||||
push_frontend_feature_flag(:realtime_labels, project)
|
||||
push_frontend_feature_flag(:work_items_mvc, project&.group)
|
||||
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
|
||||
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:merge_request_widget_graphql, project)
|
||||
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
|
||||
push_frontend_feature_flag(:issue_assignees_widget, @project)
|
||||
push_frontend_feature_flag(:realtime_labels, project)
|
||||
push_frontend_feature_flag(:refactor_security_extension, @project)
|
||||
push_frontend_feature_flag(:refactor_code_quality_inline_findings, project)
|
||||
push_frontend_feature_flag(:moved_mr_sidebar, project)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ class SearchController < ApplicationController
|
|||
|
||||
RESCUE_FROM_TIMEOUT_ACTIONS = [:count, :show, :autocomplete, :aggregations].freeze
|
||||
|
||||
track_event :show, name: 'i_search_total', destinations: [:redis_hll, :snowplow]
|
||||
track_custom_event :show,
|
||||
name: 'i_search_total',
|
||||
label: 'redis_hll_counters.search.search_total_unique_counts_monthly',
|
||||
action: 'executed',
|
||||
destinations: [:redis_hll, :snowplow]
|
||||
|
||||
def self.search_rate_limited_endpoints
|
||||
%i[show count autocomplete]
|
||||
|
|
@ -243,6 +247,10 @@ class SearchController < ApplicationController
|
|||
search_service.project&.namespace || search_service.group
|
||||
end
|
||||
|
||||
def tracking_project_source
|
||||
search_service.project
|
||||
end
|
||||
|
||||
def search_type
|
||||
'basic'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,15 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Normally this wouldn't be needed and we could use
|
||||
#
|
||||
# type Types::IssueType.connection_type, null: true
|
||||
# in a resolver. However we can end up with cyclic definitions,
|
||||
# which can result in errors like
|
||||
#
|
||||
# in a resolver. However we can end up with cyclic definitions.
|
||||
# Running the spec locally can result in errors like
|
||||
#
|
||||
# NameError: uninitialized constant Resolvers::GroupIssuesResolver
|
||||
#
|
||||
# Now we would use
|
||||
# or other errors. To fix this, we created this file and use
|
||||
#
|
||||
# type "Types::IssueConnection", null: true
|
||||
#
|
||||
# which gives a delayed resolution, and the proper connection type.
|
||||
#
|
||||
# See app/graphql/resolvers/base_issues_resolver.rb
|
||||
# Reference: https://github.com/rmosolgo/graphql-ruby/issues/3974#issuecomment-1084444214
|
||||
|
||||
# and https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#testing-tips-and-tricks
|
||||
#
|
||||
Types::IssueConnection = Types::IssueType.connection_type
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ module IssuesHelper
|
|||
def issue_css_classes(issue)
|
||||
classes = ["issue"]
|
||||
classes << "closed" if issue.closed?
|
||||
classes << "today" if issue.new?
|
||||
classes << "gl-cursor-grab" if @sort == 'relative_position'
|
||||
classes.join(' ')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -464,18 +464,6 @@ module Issuable
|
|||
end
|
||||
end
|
||||
|
||||
def today?
|
||||
Date.today == created_at.to_date
|
||||
end
|
||||
|
||||
def created_hours_ago
|
||||
(Time.now.utc.to_i - created_at.utc.to_i) / 3600
|
||||
end
|
||||
|
||||
def new?
|
||||
created_hours_ago < 24
|
||||
end
|
||||
|
||||
def open?
|
||||
opened?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class WebHookLog < ApplicationRecord
|
|||
|
||||
def redact_user_emails
|
||||
self.request_data.deep_transform_values! do |value|
|
||||
value =~ URI::MailTo::EMAIL_REGEXP ? _('[REDACTED]') : value
|
||||
value.to_s =~ URI::MailTo::EMAIL_REGEXP ? _('[REDACTED]') : value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: realtime_labels
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83743
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357370
|
||||
milestone: '14.10'
|
||||
type: development
|
||||
group: group::project management
|
||||
default_enabled: true
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'google/apis/core/http_command'
|
||||
require 'google/apis/version'
|
||||
|
||||
raise 'This patch is only tested with google-api-client-ruby v0.50.0' unless Google::Apis::VERSION == "0.50.0"
|
||||
raise 'This patch is only tested with google-api-client-ruby v0.53.0' unless Google::Apis::VERSION == "0.53.0"
|
||||
|
||||
# The google-api-ruby-client does not have a way to increase or disable
|
||||
# the maximum allowed time for a request to be retried. By default, it
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
- name: "Changing merge request approvals with the `/approvals` API endpoint"
|
||||
announcement_milestone: "12.3"
|
||||
announcement_date: "2019-09-22"
|
||||
removal_milestone: "16.0"
|
||||
removal_date: "2023-03-22"
|
||||
breaking_change: true
|
||||
reporter: tlinz
|
||||
stage: Create
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353097
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
To change the approvals required for a merge request, you should no longer use the `/approvals` API endpoint, which was deprecated in GitLab 12.3.
|
||||
|
||||
Instead, use the [`/approval_rules` endpoint](https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals) to [create](https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule) or [update](https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-merge-request-level-rule) the approval rules for a merge request.
|
||||
#
|
||||
# OPTIONAL FIELDS
|
||||
#
|
||||
tiers: Premium
|
||||
documentation_url: https://docs.gitlab.com/ee/api/merge_request_approvals.html
|
||||
image_url: # (optional) This is a link to a thumbnail image depicting the feature
|
||||
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
||||
|
|
@ -8581,6 +8581,52 @@ The edge type for [`PipelineSecurityReportFinding`](#pipelinesecurityreportfindi
|
|||
| <a id="pipelinesecurityreportfindingedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="pipelinesecurityreportfindingedgenode"></a>`node` | [`PipelineSecurityReportFinding`](#pipelinesecurityreportfinding) | The item at the end of the edge. |
|
||||
|
||||
#### `ProductAnalyticsDashboardConnection`
|
||||
|
||||
The connection type for [`ProductAnalyticsDashboard`](#productanalyticsdashboard).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="productanalyticsdashboardconnectionedges"></a>`edges` | [`[ProductAnalyticsDashboardEdge]`](#productanalyticsdashboardedge) | A list of edges. |
|
||||
| <a id="productanalyticsdashboardconnectionnodes"></a>`nodes` | [`[ProductAnalyticsDashboard]`](#productanalyticsdashboard) | A list of nodes. |
|
||||
| <a id="productanalyticsdashboardconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `ProductAnalyticsDashboardEdge`
|
||||
|
||||
The edge type for [`ProductAnalyticsDashboard`](#productanalyticsdashboard).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="productanalyticsdashboardedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="productanalyticsdashboardedgenode"></a>`node` | [`ProductAnalyticsDashboard`](#productanalyticsdashboard) | The item at the end of the edge. |
|
||||
|
||||
#### `ProductAnalyticsDashboardWidgetConnection`
|
||||
|
||||
The connection type for [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="productanalyticsdashboardwidgetconnectionedges"></a>`edges` | [`[ProductAnalyticsDashboardWidgetEdge]`](#productanalyticsdashboardwidgetedge) | A list of edges. |
|
||||
| <a id="productanalyticsdashboardwidgetconnectionnodes"></a>`nodes` | [`[ProductAnalyticsDashboardWidget]`](#productanalyticsdashboardwidget) | A list of nodes. |
|
||||
| <a id="productanalyticsdashboardwidgetconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `ProductAnalyticsDashboardWidgetEdge`
|
||||
|
||||
The edge type for [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="productanalyticsdashboardwidgetedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="productanalyticsdashboardwidgetedgenode"></a>`node` | [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget) | The item at the end of the edge. |
|
||||
|
||||
#### `ProjectConnection`
|
||||
|
||||
The connection type for [`Project`](#project).
|
||||
|
|
@ -16483,6 +16529,29 @@ Represents vulnerability finding of a security report on the pipeline.
|
|||
| <a id="previewbillableuserchangenewbillableusercount"></a>`newBillableUserCount` | [`Int`](#int) | Total number of billable users after change. |
|
||||
| <a id="previewbillableuserchangeseatsinsubscription"></a>`seatsInSubscription` | [`Int`](#int) | Number of seats in subscription. |
|
||||
|
||||
### `ProductAnalyticsDashboard`
|
||||
|
||||
Represents a product analytics dashboard.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="productanalyticsdashboarddescription"></a>`description` | [`String`](#string) | Description of the dashboard. |
|
||||
| <a id="productanalyticsdashboardtitle"></a>`title` | [`String!`](#string) | Title of the dashboard. |
|
||||
| <a id="productanalyticsdashboardwidgets"></a>`widgets` | [`ProductAnalyticsDashboardWidgetConnection!`](#productanalyticsdashboardwidgetconnection) | Widgets shown on the dashboard. (see [Connections](#connections)) |
|
||||
|
||||
### `ProductAnalyticsDashboardWidget`
|
||||
|
||||
Represents a product analytics dashboard widget.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="productanalyticsdashboardwidgetgridattributes"></a>`gridAttributes` | [`JSON`](#json) | Description of the position and size of the widget. |
|
||||
| <a id="productanalyticsdashboardwidgettitle"></a>`title` | [`String!`](#string) | Title of the widget. |
|
||||
|
||||
### `Project`
|
||||
|
||||
#### Fields
|
||||
|
|
@ -17401,6 +17470,26 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="projectpipelinesupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Pipelines updated before this date. |
|
||||
| <a id="projectpipelinesusername"></a>`username` | [`String`](#string) | Filter pipelines by the user that triggered the pipeline. |
|
||||
|
||||
##### `Project.productAnalyticsDashboards`
|
||||
|
||||
Product Analytics dashboards of the project.
|
||||
|
||||
WARNING:
|
||||
**Introduced** in 15.6.
|
||||
This feature is in Alpha. It can be changed or removed at any time.
|
||||
|
||||
Returns [`ProductAnalyticsDashboardConnection`](#productanalyticsdashboardconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectproductanalyticsdashboardsslug"></a>`slug` | [`String`](#string) | Find by dashboard slug. |
|
||||
|
||||
##### `Project.projectMembers`
|
||||
|
||||
Members of the project.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ info: "To determine the technical writer assigned to the Stage/Group associated
|
|||
|
||||
# Merge request approvals API **(PREMIUM)**
|
||||
|
||||
> Changing approval configuration with the `/approvals` endpoint was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3.
|
||||
|
||||
Configuration for
|
||||
[approvals on all merge requests](../user/project/merge_requests/approvals/index.md)
|
||||
in the project. Must be authenticated for all endpoints.
|
||||
|
|
@ -57,7 +59,7 @@ Supported attributes:
|
|||
| Attribute | Type | Required | Description |
|
||||
| ------------------------------------------------ | ------- | -------- | -- |
|
||||
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
|
||||
| `approvals_before_merge` | integer | **{dotted-circle}** No | How many approvals are required before a merge request can be merged. Deprecated in GitLab 12.0 in favor of Approval Rules API. |
|
||||
| `approvals_before_merge` (deprecated) | integer | **{dotted-circle}** No | How many approvals are required before a merge request can be merged. [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. |
|
||||
| `disable_overriding_approvers_per_merge_request` | boolean | **{dotted-circle}** No | Allow or prevent overriding approvers per merge request. |
|
||||
| `merge_requests_author_approval` | boolean | **{dotted-circle}** No | Allow or prevent authors from self approving merge requests; `true` means authors can self approve. |
|
||||
| `merge_requests_disable_committers_approval` | boolean | **{dotted-circle}** No | Allow or prevent committers from self approving merge requests. |
|
||||
|
|
@ -582,9 +584,16 @@ Supported attributes:
|
|||
}
|
||||
```
|
||||
|
||||
### Change approval configuration
|
||||
### Change approval configuration (deprecated)
|
||||
|
||||
> Moved to GitLab Premium in 13.9.
|
||||
> - Moved to GitLab Premium in GitLab 13.9.
|
||||
> - Endpoint `/approvals` [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3.
|
||||
|
||||
WARNING:
|
||||
The `/approvals` endpoint was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3
|
||||
and is planned for removal in 16.0. To change the approvals required for a merge request,
|
||||
use the `/approval_rules` endpoint described in [Create merge request level rule](#create-merge-request-level-rule).
|
||||
on this page. This change is a breaking change.
|
||||
|
||||
If you are allowed to, you can change `approvals_required` using the following
|
||||
endpoint:
|
||||
|
|
@ -598,7 +607,7 @@ Supported attributes:
|
|||
| Attribute | Type | Required | Description |
|
||||
|----------------------|-------------------|----------|-------------|
|
||||
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
|
||||
| `approvals_required` | integer | **{check-circle}** Yes | Approvals required before MR can be merged. Deprecated in GitLab 12.0 in favor of Approval Rules API. |
|
||||
| `approvals_required` | integer | **{check-circle}** Yes | Approvals required before MR can be merged. |
|
||||
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
|
||||
|
||||
```json
|
||||
|
|
|
|||
|
|
@ -121,8 +121,8 @@ defined external service. This includes confidential merge requests.
|
|||
| Attribute | Type | Required | Description |
|
||||
|------------------------|------------------|----------|------------------------------------------------|
|
||||
| `id` | integer | yes | ID of a project |
|
||||
| `name` | string | yes | Display name of status check |
|
||||
| `external_url` | string | yes | URL of status check resource |
|
||||
| `name` | string | yes | Display name of external status check |
|
||||
| `external_url` | string | yes | URL of external status check resource |
|
||||
| `protected_branch_ids` | `array<Integer>` | no | IDs of protected branches to scope the rule by |
|
||||
|
||||
## Delete external status check
|
||||
|
|
@ -135,7 +135,7 @@ DELETE /projects/:id/external_status_checks/:check_id
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
|------------------------|----------------|----------|-----------------------|
|
||||
| `rule_id` | integer | yes | ID of an status check |
|
||||
| `check_id` | integer | yes | ID of an external status check |
|
||||
| `id` | integer | yes | ID of a project |
|
||||
|
||||
## Update external status check
|
||||
|
|
@ -149,8 +149,8 @@ PUT /projects/:id/external_status_checks/:check_id
|
|||
| Attribute | Type | Required | Description |
|
||||
|------------------------|------------------|----------|------------------------------------------------|
|
||||
| `id` | integer | yes | ID of a project |
|
||||
| `rule_id` | integer | yes | ID of an external status check |
|
||||
| `name` | string | no | Display name of status check |
|
||||
| `check_id` | integer | yes | ID of an external status check |
|
||||
| `name` | string | no | Display name of external status check |
|
||||
| `external_url` | string | no | URL of external status check resource |
|
||||
| `protected_branch_ids` | `array<Integer>` | no | IDs of protected branches to scope the rule by |
|
||||
|
||||
|
|
|
|||
|
|
@ -2178,32 +2178,44 @@ end
|
|||
|
||||
```ruby
|
||||
NameError: uninitialized constant Resolvers::GroupIssuesResolver
|
||||
|
||||
or
|
||||
|
||||
GraphQL::Pagination::Connections::ImplementationMissingError
|
||||
```
|
||||
|
||||
though you might see something different.
|
||||
|
||||
To fix this, we must create a new file that encapsulates the connection type,
|
||||
and then reference it using double quotes. This gives a delayed resolution,
|
||||
and the proper connection type. For example:
|
||||
|
||||
```ruby
|
||||
module Types
|
||||
# rubocop: disable Graphql/AuthorizeTypes
|
||||
class IssueConnectionType < CountableConnectionType
|
||||
end
|
||||
end
|
||||
[app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb)
|
||||
originally contained the line
|
||||
|
||||
Types::IssueConnectionType.prepend_mod_with('Types::IssueConnectionType')
|
||||
```ruby
|
||||
type Types::IssueType.connection_type, null: true
|
||||
```
|
||||
|
||||
in [types/issue_connection_type.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/issue_connection_type.rb)
|
||||
defines a new `Types::IssueConnectionType`, and is then referenced in
|
||||
[app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb)
|
||||
Running the specs locally for this file caused the
|
||||
`NameError: uninitialized constant Resolvers::GroupIssuesResolver` error.
|
||||
|
||||
The fix was to create a new file, [app/graphql/types/issue_connection.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/issue_connection.rb) with the
|
||||
line:
|
||||
|
||||
```ruby
|
||||
Types::IssueConnection = Types::IssueType.connection_type
|
||||
```
|
||||
|
||||
and in [app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb)
|
||||
we use the line
|
||||
|
||||
```ruby
|
||||
type "Types::IssueConnection", null: true
|
||||
```
|
||||
|
||||
Only use this style if you are having spec failures. This is not intended to be a new
|
||||
pattern that we use. This issue may disappear after we've upgraded to `2.x`.
|
||||
pattern that we use. This issue should disappear after we've upgraded to `2.x`.
|
||||
|
||||
- There can be instances where a spec fails because the class is not loaded correctly.
|
||||
It relates to the
|
||||
|
|
|
|||
|
|
@ -496,8 +496,6 @@ metric counters.
|
|||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:---------------------------------------------------------------------------|:--------------|:---------|:-----------------------------------------------------------------------------------------------------------------|
|
||||
| `gitops_sync_count` (DEPRECATED) | integer | no | The number to increase the `gitops_sync` counter by |
|
||||
| `k8s_api_proxy_request_count` (DEPRECATED) | integer | no | The number to increase the `k8s_api_proxy_request` counter by |
|
||||
| `counters` | hash | no | The number to increase the `k8s_api_proxy_request` counter by |
|
||||
| `counters["k8s_api_proxy_request"]` | integer | no | The number to increase the `k8s_api_proxy_request` counter by |
|
||||
| `counters["gitops_sync"]` | integer | no | The number to increase the `gitops_sync` counter by |
|
||||
|
|
@ -512,7 +510,7 @@ Example Request:
|
|||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Content-Type: application/json" \
|
||||
--data '{"gitops_sync_count":1}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
|
||||
--data '{"counters": {"gitops_sync":1}}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
|
||||
```
|
||||
|
||||
### Create Starboard vulnerability
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ Any such changes lead to inconsistent reports from multiple GitLab instances.
|
|||
If there is a problem with an existing metric, it's best to deprecate the existing metric,
|
||||
and use it, side by side, with the desired new metric.
|
||||
|
||||
If you do need to change a metric, please notify the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) teams by `@` mentioning those groups in a comment on the MR.
|
||||
Many Service Ping metrics are relied upon for health score and XMAU reporting and
|
||||
unexpected changes to those metrics could break reporting.
|
||||
|
||||
Example:
|
||||
Consider following change. Before GitLab 12.6, the `example_metric` was implemented as:
|
||||
|
||||
|
|
@ -135,3 +139,6 @@ To remove a metric:
|
|||
1. Remove any other records related to the metric:
|
||||
- The feature flag YAML file at [`config/feature_flags/*/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/config/feature_flags).
|
||||
- The entry in the known events YAML file at [`lib/gitlab/usage_data_counters/known_events/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/usage_data_counters/known_events).
|
||||
|
||||
1. Notify the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) by `@` mentioning those groups in a comment on the MR.
|
||||
Many Service Ping metrics are relied upon for health score and XMAU reporting and unexpected changes to those metrics could break reporting.
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ are regular backend changes.
|
|||
Read the [stages file](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml).
|
||||
- Check the file location. Consider the time frame, and if the file should be under `ee`.
|
||||
- Check the tiers.
|
||||
- If a metric was changed or removed: Make sure the MR author notified the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) by `@` mentioning those groups in a comment on the MR.
|
||||
- Metrics instrumentations
|
||||
- Recommend using metrics instrumentation for new metrics, [if possible](metrics_instrumentation.md#support-for-instrumentation-classes).
|
||||
- Approve the MR, and relabel the MR with `~"product intelligence::approved"`.
|
||||
|
|
|
|||
|
|
@ -31,6 +31,18 @@ For removal reviewers (Technical Writers only):
|
|||
https://about.gitlab.com/handbook/marketing/blog/release-posts/#update-the-removals-doc
|
||||
-->
|
||||
|
||||
## Removed in 16.0
|
||||
|
||||
### Changing merge request approvals with the `/approvals` API endpoint
|
||||
|
||||
WARNING:
|
||||
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
||||
Review the details carefully before upgrading.
|
||||
|
||||
To change the approvals required for a merge request, you should no longer use the `/approvals` API endpoint, which was deprecated in GitLab 12.3.
|
||||
|
||||
Instead, use the [`/approval_rules` endpoint](https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals) to [create](https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule) or [update](https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-merge-request-level-rule) the approval rules for a merge request.
|
||||
|
||||
## Removed in 15.4
|
||||
|
||||
### SAST analyzer consolidation and CI/CD template changes
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ group: Static Analysis
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Infrastructure as Code (IaC) Scanning
|
||||
# Infrastructure as Code (IaC) Scanning **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6655) in GitLab 14.5.
|
||||
|
||||
|
|
|
|||
|
|
@ -46,5 +46,9 @@ To remove an award emoji, select the emoji again.
|
|||
You can upload custom emojis to a GitLab instance with the GraphQL API.
|
||||
For more, visit [Use custom emojis with GraphQL](../api/graphql/custom_emoji.md).
|
||||
|
||||
Custom emojis don't show in the emoji picker.
|
||||
To use them in a text box, type the filename without the extension and surrounded by colons.
|
||||
For example, for a file named `thank-you.png`, type `:thank-you:`.
|
||||
|
||||
For the list of custom emojis available for GitLab.com, visit
|
||||
[the `custom_emoji` project](https://gitlab.com/custom_emoji/custom_emoji/-/tree/main/img).
|
||||
|
|
|
|||
|
|
@ -673,7 +673,7 @@ you can see the change without having to refresh the page.
|
|||
The following sections are updated in real time:
|
||||
|
||||
- [Assignee](#assignee)
|
||||
- Labels, [if enabled](../labels.md#real-time-changes-to-labels)
|
||||
- [Labels](../labels.md#assign-and-unassign-labels)
|
||||
|
||||
## Assignee
|
||||
|
||||
|
|
|
|||
|
|
@ -28,10 +28,21 @@ You can use two types of labels in GitLab:
|
|||
|
||||
## Assign and unassign labels
|
||||
|
||||
> Unassigning labels with the **X** button [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216881) in GitLab 13.5.
|
||||
> - Unassigning labels with the **X** button [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216881) in GitLab 13.5.
|
||||
> - Real-time updates in the sidebar [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241538) in GitLab 14.10 with a [feature flag](../../administration/feature_flags.md) named `realtime_labels`, disabled by default.
|
||||
> - Real-time updates in the sidebar [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/357370#note_991987201) in GitLab 15.1.
|
||||
> - Real-time updates in the sidebar [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/357370) in GitLab 15.5.
|
||||
> - Real-time updates in the sidebar [generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103199) in GitLab 15.6. Feature flag `realtime_labels` removed.
|
||||
|
||||
You can assign labels to any issue, merge request, or epic.
|
||||
|
||||
Changed labels are immediately visible to other users, without refreshing the page, on the following:
|
||||
|
||||
- Epics
|
||||
- Incidents
|
||||
- Issues
|
||||
- Merge requests
|
||||
|
||||
To assign or unassign a label:
|
||||
|
||||
1. In the **Labels** section of the sidebar, select **Edit**.
|
||||
|
|
@ -444,23 +455,6 @@ The labels higher in the list get higher priority.
|
|||
To learn what happens when you sort by priority or label priority, see
|
||||
[Sorting and ordering issue lists](issues/sorting_issue_lists.md).
|
||||
|
||||
## Real-time changes to labels
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241538) in GitLab 14.10 with a [feature flag](../../administration/feature_flags.md) named `realtime_labels`, disabled by default.
|
||||
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/357370#note_991987201) in GitLab 15.1.
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/357370) in GitLab 15.5.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, to prevent updating labels in real-time, you can ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `realtime_labels`.
|
||||
On GitLab.com, this feature is available.
|
||||
|
||||
Changed labels are immediately visible to other users, without refreshing the page, on the following:
|
||||
|
||||
- Epics
|
||||
- Incidents
|
||||
- Issues
|
||||
- Merge requests
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Some label titles end with `_duplicate<number>`
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ module API
|
|||
mount ::API::FreezePeriods
|
||||
mount ::API::GroupClusters
|
||||
mount ::API::GroupExport
|
||||
mount ::API::GroupVariables
|
||||
mount ::API::ImportBitbucketServer
|
||||
mount ::API::ImportGithub
|
||||
mount ::API::Keys
|
||||
|
|
@ -272,7 +273,6 @@ module API
|
|||
mount ::API::GroupLabels
|
||||
mount ::API::GroupMilestones
|
||||
mount ::API::GroupPackages
|
||||
mount ::API::GroupVariables
|
||||
mount ::API::Groups
|
||||
mount ::API::HelmPackages
|
||||
mount ::API::Integrations
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ module API
|
|||
helpers ::API::Helpers::VariablesHelpers
|
||||
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a group'
|
||||
requires :id, type: String, desc: 'The ID of a group or URL-encoded path of the group owned by the authenticated
|
||||
user'
|
||||
end
|
||||
|
||||
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||||
desc 'Get group-level variables' do
|
||||
desc 'Get a list of group-level variables' do
|
||||
success Entities::Ci::Variable
|
||||
tags %w[ci_variables]
|
||||
end
|
||||
params do
|
||||
use :pagination
|
||||
|
|
@ -26,8 +28,10 @@ module API
|
|||
present paginate(variables), with: Entities::Ci::Variable
|
||||
end
|
||||
|
||||
desc 'Get a specific variable from a group' do
|
||||
desc 'Get the details of a group’s specific variable' do
|
||||
success Entities::Ci::Variable
|
||||
failure [{ code: 404, message: 'Group Variable Not Found' }]
|
||||
tags %w[ci_variables]
|
||||
end
|
||||
params do
|
||||
requires :key, type: String, desc: 'The key of the variable'
|
||||
|
|
@ -42,16 +46,19 @@ module API
|
|||
|
||||
desc 'Create a new variable in a group' do
|
||||
success Entities::Ci::Variable
|
||||
failure [{ code: 400, message: '400 Bad Request' }]
|
||||
tags %w[ci_variables]
|
||||
end
|
||||
route_setting :log_safety, { safe: %w[key], unsafe: %w[value] }
|
||||
params do
|
||||
requires :key, type: String, desc: 'The key of the variable'
|
||||
requires :value, type: String, desc: 'The value of the variable'
|
||||
requires :key, type: String, desc: 'The ID of a group or URL-encoded path of the group owned by the
|
||||
authenticated user'
|
||||
requires :value, type: String, desc: 'The value of a variable'
|
||||
optional :protected, type: String, desc: 'Whether the variable is protected'
|
||||
optional :masked, type: String, desc: 'Whether the variable is masked'
|
||||
optional :raw, type: String, desc: 'Whether the variable will be expanded'
|
||||
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
|
||||
|
||||
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var'
|
||||
optional :environment_scope, type: String, desc: 'The environment scope of a variable'
|
||||
use :optional_group_variable_params_ee
|
||||
end
|
||||
post ':id/variables' do
|
||||
|
|
@ -75,15 +82,18 @@ module API
|
|||
|
||||
desc 'Update an existing variable from a group' do
|
||||
success Entities::Ci::Variable
|
||||
failure [{ code: 400, message: '400 Bad Request' }, { code: 404, message: 'Group Variable Not Found' }]
|
||||
tags %w[ci_variables]
|
||||
end
|
||||
route_setting :log_safety, { safe: %w[key], unsafe: %w[value] }
|
||||
params do
|
||||
optional :key, type: String, desc: 'The key of the variable'
|
||||
optional :value, type: String, desc: 'The value of the variable'
|
||||
optional :key, type: String, desc: 'The key of a variable'
|
||||
optional :value, type: String, desc: 'The value of a variable'
|
||||
optional :protected, type: String, desc: 'Whether the variable is protected'
|
||||
optional :masked, type: String, desc: 'Whether the variable is masked'
|
||||
optional :raw, type: String, desc: 'Whether the variable will be expanded'
|
||||
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
|
||||
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var'
|
||||
optional :environment_scope, type: String, desc: 'The environment scope of a variable'
|
||||
|
||||
use :optional_group_variable_params_ee
|
||||
end
|
||||
|
|
@ -110,9 +120,11 @@ module API
|
|||
|
||||
desc 'Delete an existing variable from a group' do
|
||||
success Entities::Ci::Variable
|
||||
failure [{ code: 404, message: 'Group Variable Not Found' }]
|
||||
tags %w[ci_variables]
|
||||
end
|
||||
params do
|
||||
requires :key, type: String, desc: 'The key of the variable'
|
||||
requires :key, type: String, desc: 'The key of a variable'
|
||||
end
|
||||
delete ':id/variables/:key' do
|
||||
variable = find_variable(user_group, params)
|
||||
|
|
|
|||
|
|
@ -61,15 +61,6 @@ module API
|
|||
Guest.can?(:download_code, project) || agent.has_access_to?(project)
|
||||
end
|
||||
|
||||
def count_events
|
||||
strong_memoize(:count_events) do
|
||||
events = params.slice(:gitops_sync_count, :k8s_api_proxy_request_count)
|
||||
events.transform_keys! { |event| event.to_s.chomp('_count') }
|
||||
events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request) unless events.present?
|
||||
events
|
||||
end
|
||||
end
|
||||
|
||||
def increment_unique_events
|
||||
events = params[:unique_counters]&.slice(:agent_users_using_ci_tunnel)
|
||||
|
||||
|
|
@ -77,6 +68,12 @@ module API
|
|||
increment_unique_values(event, entity_ids)
|
||||
end
|
||||
end
|
||||
|
||||
def increment_count_events
|
||||
events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request)
|
||||
|
||||
Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(events)
|
||||
end
|
||||
end
|
||||
|
||||
namespace 'internal' do
|
||||
|
|
@ -144,26 +141,17 @@ module API
|
|||
detail 'Updates usage metrics for agent'
|
||||
end
|
||||
params do
|
||||
# Todo: Remove gitops_sync_count and k8s_api_proxy_request_count in the next milestone
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/369489
|
||||
# We're only keeping it for backwards compatibility until KAS is released
|
||||
# using `counts:` instead
|
||||
optional :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by'
|
||||
optional :k8s_api_proxy_request_count, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by'
|
||||
optional :counters, type: Hash do
|
||||
optional :gitops_sync, type: Integer, desc: 'The count to increment the gitops_sync metric by'
|
||||
optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by'
|
||||
optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request metric by'
|
||||
end
|
||||
mutually_exclusive :counters, :gitops_sync_count
|
||||
mutually_exclusive :counters, :k8s_api_proxy_request_count
|
||||
|
||||
optional :unique_counters, type: Hash do
|
||||
optional :agent_users_using_ci_tunnel, type: Set[Integer], desc: 'A set of user ids that have interacted a CI Tunnel to'
|
||||
end
|
||||
end
|
||||
post '/' do
|
||||
Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(count_events) if count_events
|
||||
|
||||
increment_count_events
|
||||
increment_unique_events
|
||||
|
||||
no_content!
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
attr_reader :json_data, :report, :validate
|
||||
attr_reader :json_data, :report, :validate, :project
|
||||
|
||||
def valid?
|
||||
return true unless validate
|
||||
|
|
|
|||
|
|
@ -156,6 +156,14 @@ module Gitlab
|
|||
signatures.present?
|
||||
end
|
||||
|
||||
def false_positive?
|
||||
flags.any?(&:false_positive?)
|
||||
end
|
||||
|
||||
def remediation_byte_offsets
|
||||
remediations.map(&:byte_offsets).compact
|
||||
end
|
||||
|
||||
def raw_metadata
|
||||
@raw_metadata ||= original_data.to_json
|
||||
end
|
||||
|
|
@ -176,6 +184,10 @@ module Gitlab
|
|||
original_data['location']
|
||||
end
|
||||
|
||||
def assets
|
||||
original_data['assets'] || []
|
||||
end
|
||||
|
||||
# Returns either the max priority signature hex
|
||||
# or the location fingerprint
|
||||
def location_fingerprint
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ module Gitlab
|
|||
description: description
|
||||
}.compact
|
||||
end
|
||||
|
||||
def false_positive?
|
||||
flag_type == :false_positive
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -171,16 +171,6 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def strong_memoize_with(name, *args)
|
||||
container = strong_memoize(name) { {} }
|
||||
|
||||
if container.key?(args)
|
||||
container[args]
|
||||
else
|
||||
container[args] = yield
|
||||
end
|
||||
end
|
||||
|
||||
def release
|
||||
return unless @pipeline.tag?
|
||||
|
||||
|
|
|
|||
|
|
@ -9,19 +9,23 @@ module Gitlab
|
|||
'<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>'
|
||||
|
||||
def check
|
||||
unsupported_database = Gitlab::Database
|
||||
unsupported_databases = Gitlab::Database
|
||||
.database_base_models
|
||||
.map { |_, model| Gitlab::Database::Reflection.new(model) }
|
||||
.reject(&:postgresql_minimum_supported_version?)
|
||||
.each_with_object({}) do |(database_name, base_model), databases|
|
||||
database = Gitlab::Database::Reflection.new(base_model)
|
||||
|
||||
unsupported_database.map do |database|
|
||||
databases[database_name] = database unless database.postgresql_minimum_supported_version?
|
||||
end
|
||||
|
||||
unsupported_databases.map do |database_name, database|
|
||||
{
|
||||
type: 'warning',
|
||||
message: _('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \
|
||||
'%{pg_version_minimum} is required for this version of GitLab. ' \
|
||||
message: _('Database \'%{database_name}\' is using PostgreSQL %{pg_version_current}, ' \
|
||||
'but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. ' \
|
||||
'Please upgrade your environment to a supported PostgreSQL version, ' \
|
||||
'see %{pg_requirements_url} for details.') % \
|
||||
{
|
||||
database_name: database_name,
|
||||
pg_version_current: database.version,
|
||||
pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
|
||||
pg_requirements_url: PG_REQUIREMENTS_LINK
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ module Gitlab
|
|||
|
||||
class << self
|
||||
def increment_event_counts(events)
|
||||
return unless events.present?
|
||||
|
||||
validate!(events)
|
||||
|
||||
events.each do |event, incr|
|
||||
|
|
|
|||
|
|
@ -45,6 +45,16 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def strong_memoize_with(name, *args)
|
||||
container = strong_memoize(name) { {} }
|
||||
|
||||
if container.key?(args)
|
||||
container[args]
|
||||
else
|
||||
container[args] = yield
|
||||
end
|
||||
end
|
||||
|
||||
def strong_memoized?(name)
|
||||
instance_variable_defined?(ivar(name))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12597,6 +12597,9 @@ msgstr ""
|
|||
msgid "Data type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Database '%{database_name}' is using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details."
|
||||
msgstr ""
|
||||
|
||||
msgid "Database update failed"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32257,6 +32260,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|Users can only push commits to this repository if the committer email is one of their own verified emails."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Users can only push commits to this repository if the committer name is consistent with their git config username."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Users can request access"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33295,6 +33301,9 @@ msgstr ""
|
|||
msgid "PushRule|Push rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "PushRule|Reject inconsistent user name"
|
||||
msgstr ""
|
||||
|
||||
msgid "PushRule|Reject unverified users"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -46436,9 +46445,6 @@ msgstr ""
|
|||
msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
|
||||
msgstr ""
|
||||
|
||||
msgid "You are using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details."
|
||||
msgstr ""
|
||||
|
||||
msgid "You can %{gitlabLinkStart}resolve conflicts on GitLab%{gitlabLinkEnd} or %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -223,7 +223,14 @@ RSpec.describe SearchController do
|
|||
|
||||
let(:project) { nil }
|
||||
let(:category) { described_class.to_s }
|
||||
let(:action) { 'i_search_total' }
|
||||
let(:action) { 'executed' }
|
||||
let(:label) { 'redis_hll_counters.search.search_total_unique_counts_monthly' }
|
||||
let(:property) { 'i_search_total' }
|
||||
let(:context) do
|
||||
[Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll,
|
||||
event: property).to_context]
|
||||
end
|
||||
|
||||
let(:namespace) { create(:group) }
|
||||
let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Admin::HookLogs' do
|
||||
let(:project) { create(:project) }
|
||||
let(:system_hook) { create(:system_hook) }
|
||||
let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
|
||||
let_it_be(:system_hook) { create(:system_hook) }
|
||||
let_it_be(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
|
||||
before do
|
||||
admin = create(:admin)
|
||||
sign_in(admin)
|
||||
gitlab_enable_admin_mode_sign_in(admin)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe 'Admin::Hooks' do
|
||||
include Spec::Support::Helpers::ModalHelpers
|
||||
|
||||
let(:user) { create(:admin) }
|
||||
let_it_be(:user) { create(:admin) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ describe('LabelsSelectRoot', () => {
|
|||
issuableType = IssuableType.Issue,
|
||||
queryHandler = successfulQueryHandler,
|
||||
mutationHandler = successfulMutationHandler,
|
||||
isRealtimeEnabled = false,
|
||||
} = {}) => {
|
||||
const mockApollo = createMockApollo([
|
||||
[issueLabelsQuery, queryHandler],
|
||||
|
|
@ -74,9 +73,6 @@ describe('LabelsSelectRoot', () => {
|
|||
allowLabelEdit: true,
|
||||
allowLabelCreate: true,
|
||||
labelsManagePath: 'test',
|
||||
glFeatures: {
|
||||
realtimeLabels: isRealtimeEnabled,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -204,17 +200,10 @@ describe('LabelsSelectRoot', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('does not emit `updateSelectedLabels` event when the subscription is triggered and FF is disabled', async () => {
|
||||
it('emits `updateSelectedLabels` event when the subscription is triggered', async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('updateSelectedLabels')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('emits `updateSelectedLabels` event when the subscription is triggered and FF is enabled', async () => {
|
||||
createComponent({ isRealtimeEnabled: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('updateSelectedLabels')).toEqual([
|
||||
[
|
||||
{
|
||||
|
|
|
|||
|
|
@ -543,24 +543,6 @@ describe('IssuableItem', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when issuable was created within the past 24 hours', () => {
|
||||
it('renders issuable card with a recently-created style', () => {
|
||||
wrapper = createComponent({
|
||||
issuable: { ...mockIssuable, createdAt: '2020-12-10T12:34:56' },
|
||||
});
|
||||
|
||||
expect(wrapper.classes()).toContain('today');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when issuable was created earlier than the past 24 hours', () => {
|
||||
it('renders issuable card without a recently-created style', () => {
|
||||
wrapper = createComponent({ issuable: { ...mockIssuable, createdAt: '2020-12-09' } });
|
||||
|
||||
expect(wrapper.classes()).not.toContain('today');
|
||||
});
|
||||
});
|
||||
|
||||
describe('scoped labels', () => {
|
||||
describe.each`
|
||||
description | labelPosition | hasScopedLabelsFeature | scoped
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import EditedAt from '~/issues/show/components/edited.vue';
|
|||
import { updateDraft } from '~/lib/utils/autosave';
|
||||
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
|
||||
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
|
||||
import WorkItemDescriptionRendered from '~/work_items/components/work_item_description_rendered.vue';
|
||||
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
|
||||
|
|
@ -37,12 +38,19 @@ describe('WorkItemDescription', () => {
|
|||
const subscriptionHandler = jest.fn().mockResolvedValue(workItemDescriptionSubscriptionResponse);
|
||||
const workItemByIidResponseHandler = jest.fn().mockResolvedValue(projectWorkItemResponse);
|
||||
let workItemResponseHandler;
|
||||
let workItemsMvc2;
|
||||
|
||||
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
|
||||
const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor);
|
||||
const findRenderedDescription = () => wrapper.findComponent(WorkItemDescriptionRendered);
|
||||
const findEditedAt = () => wrapper.findComponent(EditedAt);
|
||||
|
||||
const editDescription = (newText) => wrapper.find('textarea').setValue(newText);
|
||||
const editDescription = (newText) => {
|
||||
if (workItemsMvc2) {
|
||||
return findMarkdownEditor().vm.$emit('input', newText);
|
||||
}
|
||||
return wrapper.find('textarea').setValue(newText);
|
||||
};
|
||||
|
||||
const clickCancel = () => wrapper.find('[data-testid="cancel"]').vm.$emit('click');
|
||||
const clickSave = () => wrapper.find('[data-testid="save-description"]').vm.$emit('click', {});
|
||||
|
|
@ -72,6 +80,11 @@ describe('WorkItemDescription', () => {
|
|||
},
|
||||
fetchByIid,
|
||||
},
|
||||
provide: {
|
||||
glFeatures: {
|
||||
workItemsMvc2,
|
||||
},
|
||||
},
|
||||
stubs: {
|
||||
MarkdownField,
|
||||
},
|
||||
|
|
@ -90,175 +103,178 @@ describe('WorkItemDescription', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('has a subscription', async () => {
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(subscriptionHandler).toHaveBeenCalledWith({
|
||||
issuableId: workItemQueryResponse.data.workItem.id,
|
||||
});
|
||||
});
|
||||
|
||||
describe('editing description', () => {
|
||||
it('shows edited by text', async () => {
|
||||
const lastEditedAt = '2022-09-21T06:18:42Z';
|
||||
const lastEditedBy = {
|
||||
name: 'Administrator',
|
||||
webPath: '/root',
|
||||
};
|
||||
|
||||
await createComponent({
|
||||
workItemResponse: workItemResponseFactory({
|
||||
lastEditedAt,
|
||||
lastEditedBy,
|
||||
}),
|
||||
describe.each([true, false])(
|
||||
'editing description with workItemsMvc2 %workItemsMvc2Enabled',
|
||||
(workItemsMvc2Enabled) => {
|
||||
beforeEach(() => {
|
||||
beforeEach(() => {
|
||||
workItemsMvc2 = workItemsMvc2Enabled;
|
||||
});
|
||||
});
|
||||
|
||||
expect(findEditedAt().props()).toEqual({
|
||||
updatedAt: lastEditedAt,
|
||||
updatedByName: lastEditedBy.name,
|
||||
updatedByPath: lastEditedBy.webPath,
|
||||
});
|
||||
});
|
||||
describe('editing description', () => {
|
||||
it('shows edited by text', async () => {
|
||||
const lastEditedAt = '2022-09-21T06:18:42Z';
|
||||
const lastEditedBy = {
|
||||
name: 'Administrator',
|
||||
webPath: '/root',
|
||||
};
|
||||
|
||||
it('does not show edited by text', async () => {
|
||||
await createComponent();
|
||||
await createComponent({
|
||||
workItemResponse: workItemResponseFactory({
|
||||
lastEditedAt,
|
||||
lastEditedBy,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(findEditedAt().exists()).toBe(false);
|
||||
});
|
||||
expect(findEditedAt().props()).toEqual({
|
||||
updatedAt: lastEditedAt,
|
||||
updatedByName: lastEditedBy.name,
|
||||
updatedByPath: lastEditedBy.webPath,
|
||||
});
|
||||
});
|
||||
|
||||
it('cancels when clicking cancel', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
});
|
||||
it('does not show edited by text', async () => {
|
||||
await createComponent();
|
||||
|
||||
clickCancel();
|
||||
expect(findEditedAt().exists()).toBe(false);
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
it('cancels when clicking cancel', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
});
|
||||
|
||||
expect(confirmAction).not.toHaveBeenCalled();
|
||||
expect(findMarkdownField().exists()).toBe(false);
|
||||
});
|
||||
clickCancel();
|
||||
|
||||
it('prompts for confirmation when clicking cancel after changes', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
editDescription('updated desc');
|
||||
expect(confirmAction).not.toHaveBeenCalled();
|
||||
expect(findMarkdownField().exists()).toBe(false);
|
||||
});
|
||||
|
||||
clickCancel();
|
||||
it('prompts for confirmation when clicking cancel after changes', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
editDescription('updated desc');
|
||||
|
||||
expect(confirmAction).toHaveBeenCalled();
|
||||
});
|
||||
clickCancel();
|
||||
|
||||
it('calls update widgets mutation', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
editDescription('updated desc');
|
||||
expect(confirmAction).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
clickSave();
|
||||
it('calls update widgets mutation', async () => {
|
||||
const updatedDesc = 'updated desc';
|
||||
|
||||
await waitForPromises();
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
});
|
||||
|
||||
expect(mutationSuccessHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
id: workItemId,
|
||||
descriptionWidget: {
|
||||
description: 'updated desc',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
editDescription(updatedDesc);
|
||||
|
||||
it('tracks editing description', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
markdownPreviewPath: '/preview',
|
||||
});
|
||||
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
clickSave();
|
||||
|
||||
clickSave();
|
||||
await waitForPromises();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_description', {
|
||||
category: TRACKING_CATEGORY_SHOW,
|
||||
label: 'item_description',
|
||||
property: 'type_Task',
|
||||
});
|
||||
});
|
||||
|
||||
it('emits error when mutation returns error', async () => {
|
||||
const error = 'eror';
|
||||
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
mutationHandler: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
workItemUpdate: {
|
||||
workItem: {},
|
||||
errors: [error],
|
||||
expect(mutationSuccessHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
id: workItemId,
|
||||
descriptionWidget: {
|
||||
description: updatedDesc,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('tracks editing description', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
markdownPreviewPath: '/preview',
|
||||
});
|
||||
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||
|
||||
clickSave();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_description', {
|
||||
category: TRACKING_CATEGORY_SHOW,
|
||||
label: 'item_description',
|
||||
property: 'type_Task',
|
||||
});
|
||||
});
|
||||
|
||||
it('emits error when mutation returns error', async () => {
|
||||
const error = 'eror';
|
||||
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
mutationHandler: jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
workItemUpdate: {
|
||||
workItem: {},
|
||||
errors: [error],
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
editDescription('updated desc');
|
||||
|
||||
clickSave();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('error')).toEqual([[error]]);
|
||||
});
|
||||
|
||||
it('emits error when mutation fails', async () => {
|
||||
const error = 'eror';
|
||||
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
mutationHandler: jest.fn().mockRejectedValue(new Error(error)),
|
||||
});
|
||||
|
||||
editDescription('updated desc');
|
||||
|
||||
clickSave();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('error')).toEqual([[error]]);
|
||||
});
|
||||
|
||||
it('autosaves description', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
});
|
||||
|
||||
editDescription('updated desc');
|
||||
|
||||
expect(updateDraft).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
editDescription('updated desc');
|
||||
it('calls the global ID work item query when `fetchByIid` prop is false', async () => {
|
||||
createComponent({ fetchByIid: false });
|
||||
await waitForPromises();
|
||||
|
||||
clickSave();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('error')).toEqual([[error]]);
|
||||
});
|
||||
|
||||
it('emits error when mutation fails', async () => {
|
||||
const error = 'eror';
|
||||
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
mutationHandler: jest.fn().mockRejectedValue(new Error(error)),
|
||||
expect(workItemResponseHandler).toHaveBeenCalled();
|
||||
expect(workItemByIidResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
editDescription('updated desc');
|
||||
it('calls the IID work item query when when `fetchByIid` prop is true', async () => {
|
||||
createComponent({ fetchByIid: true });
|
||||
await waitForPromises();
|
||||
|
||||
clickSave();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('error')).toEqual([[error]]);
|
||||
});
|
||||
|
||||
it('autosaves description', async () => {
|
||||
await createComponent({
|
||||
isEditing: true,
|
||||
expect(workItemResponseHandler).not.toHaveBeenCalled();
|
||||
expect(workItemByIidResponseHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
editDescription('updated desc');
|
||||
|
||||
expect(updateDraft).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls the global ID work item query when `fetchByIid` prop is false', async () => {
|
||||
createComponent({ fetchByIid: false });
|
||||
await waitForPromises();
|
||||
|
||||
expect(workItemResponseHandler).toHaveBeenCalled();
|
||||
expect(workItemByIidResponseHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the IID work item query when when `fetchByIid` prop is true', async () => {
|
||||
createComponent({ fetchByIid: true });
|
||||
await waitForPromises();
|
||||
|
||||
expect(workItemResponseHandler).not.toHaveBeenCalled();
|
||||
expect(workItemByIidResponseHandler).toHaveBeenCalled();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,5 +29,11 @@ RSpec.describe Gitlab::Ci::Reports::Security::Flag do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#false_positive?' do
|
||||
subject { security_flag.false_positive? }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
|
|||
end
|
||||
|
||||
it 'reports deprecated database notice' do
|
||||
is_expected.to contain_exactly(notice_deprecated_database(old_database_version))
|
||||
is_expected.to contain_exactly(notice_deprecated_database('main', old_database_version))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -59,13 +59,13 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
|
|||
it 'reports deprecated database notice if the main database is using an old version' do
|
||||
allow(Gitlab::Database::Reflection).to receive(:new).with(ActiveRecord::Base).and_return(old_database)
|
||||
allow(Gitlab::Database::Reflection).to receive(:new).with(Ci::ApplicationRecord).and_return(new_database)
|
||||
is_expected.to contain_exactly(notice_deprecated_database(old_database_version))
|
||||
is_expected.to contain_exactly(notice_deprecated_database('main', old_database_version))
|
||||
end
|
||||
|
||||
it 'reports deprecated database notice if the ci database is using an old version' do
|
||||
allow(Gitlab::Database::Reflection).to receive(:new).with(ActiveRecord::Base).and_return(new_database)
|
||||
allow(Gitlab::Database::Reflection).to receive(:new).with(Ci::ApplicationRecord).and_return(old_database)
|
||||
is_expected.to contain_exactly(notice_deprecated_database(old_database_version))
|
||||
is_expected.to contain_exactly(notice_deprecated_database('ci', old_database_version))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -77,22 +77,23 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
|
|||
|
||||
it 'reports deprecated database notice' do
|
||||
is_expected.to match_array [
|
||||
notice_deprecated_database(old_database_version),
|
||||
notice_deprecated_database(old_database_version)
|
||||
notice_deprecated_database('main', old_database_version),
|
||||
notice_deprecated_database('ci', old_database_version)
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def notice_deprecated_database(database_version)
|
||||
def notice_deprecated_database(database_name, database_version)
|
||||
{
|
||||
type: 'warning',
|
||||
message: _('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \
|
||||
'%{pg_version_minimum} is required for this version of GitLab. ' \
|
||||
'Please upgrade your environment to a supported PostgreSQL version, ' \
|
||||
'see %{pg_requirements_url} for details.') % \
|
||||
message: _('Database \'%{database_name}\' is using PostgreSQL %{pg_version_current}, ' \
|
||||
'but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. ' \
|
||||
'Please upgrade your environment to a supported PostgreSQL version, ' \
|
||||
'see %{pg_requirements_url} for details.') % \
|
||||
{
|
||||
database_name: database_name,
|
||||
pg_version_current: database_version,
|
||||
pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
|
||||
pg_requirements_url: Gitlab::ConfigChecker::ExternalDatabaseChecker::PG_REQUIREMENTS_LINK
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ RSpec.describe Gitlab::UsageDataCounters::KubernetesAgentCounter do
|
|||
expect(described_class.totals).to eq(kubernetes_agent_gitops_sync: 3, kubernetes_agent_k8s_api_proxy_request: 6)
|
||||
end
|
||||
|
||||
context 'with empty events' do
|
||||
let(:events) { nil }
|
||||
|
||||
it { expect { subject }.not_to change(described_class, :totals) }
|
||||
end
|
||||
|
||||
context 'event is unknown' do
|
||||
let(:events) do
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,6 +46,13 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
|
|||
true
|
||||
end
|
||||
|
||||
def method_name_with_args(*args)
|
||||
strong_memoize_with(:method_name_with_args, args) do
|
||||
trace << [value, args]
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def trace
|
||||
@trace ||= []
|
||||
end
|
||||
|
|
@ -141,6 +148,36 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#strong_memoize_with' do
|
||||
[nil, false, true, 'value', 0, [0]].each do |value|
|
||||
context "with value #{value}" do
|
||||
let(:value) { value }
|
||||
|
||||
it 'only calls the block once' do
|
||||
value0 = object.method_name_with_args(1)
|
||||
value1 = object.method_name_with_args(1)
|
||||
value2 = object.method_name_with_args([2, 3])
|
||||
value3 = object.method_name_with_args([2, 3])
|
||||
|
||||
expect(value0).to eq(value)
|
||||
expect(value1).to eq(value)
|
||||
expect(value2).to eq(value)
|
||||
expect(value3).to eq(value)
|
||||
|
||||
expect(object.trace).to contain_exactly([value, [1]], [value, [[2, 3]]])
|
||||
end
|
||||
|
||||
it 'returns and defines the instance variable for the exact value' do
|
||||
returned_value = object.method_name_with_args(1, 2, 3)
|
||||
memoized_value = object.instance_variable_get(:@method_name_with_args)
|
||||
|
||||
expect(returned_value).to eql(value)
|
||||
expect(memoized_value).to eql({ [[1, 2, 3]] => value })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#strong_memoized?' do
|
||||
let(:value) { :anything }
|
||||
|
||||
|
|
|
|||
|
|
@ -337,31 +337,6 @@ RSpec.describe Issuable do
|
|||
it { expect(MergeRequest.to_ability_name).to eq("merge_request") }
|
||||
end
|
||||
|
||||
describe "#today?" do
|
||||
it "returns true when created today" do
|
||||
# Avoid timezone differences and just return exactly what we want
|
||||
allow(Date).to receive(:today).and_return(issue.created_at.to_date)
|
||||
expect(issue.today?).to be_truthy
|
||||
end
|
||||
|
||||
it "returns false when not created today" do
|
||||
allow(Date).to receive(:today).and_return(Date.yesterday)
|
||||
expect(issue.today?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe "#new?" do
|
||||
it "returns false when created 30 hours ago" do
|
||||
allow(issue).to receive(:created_at).and_return(Time.current - 30.hours)
|
||||
expect(issue.new?).to be_falsey
|
||||
end
|
||||
|
||||
it "returns true when created 20 hours ago" do
|
||||
allow(issue).to receive(:created_at).and_return(Time.current - 20.hours)
|
||||
expect(issue.new?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#sort_by_attribute" do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ RSpec.describe ActiveHookFilter do
|
|||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
context 'for various types of branch_filter' do
|
||||
let_it_be_with_reload(:hook) do
|
||||
create(:project_hook, push_events: true, issues_events: true)
|
||||
let(:hook) do
|
||||
build(:project_hook, push_events: true, issues_events: true)
|
||||
end
|
||||
|
||||
where(:branch_filter_strategy, :branch_filter, :ref, :expected_matches?) do
|
||||
|
|
@ -53,8 +53,8 @@ RSpec.describe ActiveHookFilter do
|
|||
end
|
||||
|
||||
context 'when the branch filter is a invalid regex' do
|
||||
let_it_be(:hook) do
|
||||
create(
|
||||
let(:hook) do
|
||||
build(
|
||||
:project_hook,
|
||||
push_events: true,
|
||||
push_events_branch_filter: 'master',
|
||||
|
|
@ -70,8 +70,8 @@ RSpec.describe ActiveHookFilter do
|
|||
end
|
||||
|
||||
context 'when the branch filter is not properly set to nil' do
|
||||
let_it_be(:hook) do
|
||||
create(
|
||||
let(:hook) do
|
||||
build(
|
||||
:project_hook,
|
||||
push_events: true,
|
||||
branch_filter_strategy: 'all_branches'
|
||||
|
|
@ -91,8 +91,8 @@ RSpec.describe ActiveHookFilter do
|
|||
stub_feature_flags(enhanced_webhook_support_regex: false)
|
||||
end
|
||||
|
||||
let_it_be(:hook) do
|
||||
create(
|
||||
let(:hook) do
|
||||
build(
|
||||
:project_hook,
|
||||
push_events: true,
|
||||
push_events_branch_filter: '(master)',
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ RSpec.describe ProjectHook do
|
|||
end
|
||||
|
||||
it_behaves_like 'includes Limitable concern' do
|
||||
subject { build(:project_hook, project: create(:project)) }
|
||||
subject { build(:project_hook) }
|
||||
end
|
||||
|
||||
describe '.for_projects' do
|
||||
it 'finds related project hooks' do
|
||||
hook_a = create(:project_hook)
|
||||
hook_b = create(:project_hook)
|
||||
hook_c = create(:project_hook)
|
||||
hook_a = create(:project_hook, project: build(:project))
|
||||
hook_b = create(:project_hook, project: build(:project))
|
||||
hook_c = create(:project_hook, project: build(:project))
|
||||
|
||||
expect(described_class.for_projects([hook_a.project, hook_b.project]))
|
||||
.to contain_exactly(hook_a, hook_b)
|
||||
|
|
@ -30,16 +30,18 @@ RSpec.describe ProjectHook do
|
|||
|
||||
describe '.push_hooks' do
|
||||
it 'returns hooks for push events only' do
|
||||
hook = create(:project_hook, push_events: true)
|
||||
create(:project_hook, push_events: false)
|
||||
project = build(:project)
|
||||
hook = create(:project_hook, project: project, push_events: true)
|
||||
create(:project_hook, project: project, push_events: false)
|
||||
expect(described_class.push_hooks).to eq([hook])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.tag_push_hooks' do
|
||||
it 'returns hooks for tag push events only' do
|
||||
hook = create(:project_hook, tag_push_events: true)
|
||||
create(:project_hook, tag_push_events: false)
|
||||
project = build(:project)
|
||||
hook = create(:project_hook, project: project, tag_push_events: true)
|
||||
create(:project_hook, project: project, tag_push_events: false)
|
||||
expect(described_class.tag_push_hooks).to eq([hook])
|
||||
end
|
||||
end
|
||||
|
|
@ -65,7 +67,7 @@ RSpec.describe ProjectHook do
|
|||
end
|
||||
|
||||
describe '#update_last_failure', :clean_gitlab_redis_shared_state do
|
||||
let_it_be(:hook) { create(:project_hook) }
|
||||
let(:hook) { build(:project_hook) }
|
||||
|
||||
it 'is a method of this class' do
|
||||
expect { hook.update_last_failure }.not_to raise_error
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ RSpec.describe SystemHook do
|
|||
end
|
||||
|
||||
describe "execute", :sidekiq_might_not_need_inline do
|
||||
let(:system_hook) { create(:system_hook) }
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, namespace: user.namespace) }
|
||||
let(:group) { create(:group) }
|
||||
let_it_be(:system_hook) { create(:system_hook) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:project) { build(:project, namespace: user.namespace) }
|
||||
let(:group) { build(:group) }
|
||||
let(:params) do
|
||||
{ name: 'John Doe', username: 'jduser', email: 'jg@example.com', password: User.random_password }
|
||||
end
|
||||
|
|
@ -145,6 +145,7 @@ RSpec.describe SystemHook do
|
|||
end
|
||||
|
||||
it 'group member update hook' do
|
||||
group = create(:group)
|
||||
group.add_guest(user)
|
||||
group.add_maintainer(user)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ RSpec.describe WebHookLog do
|
|||
it { is_expected.to validate_presence_of(:web_hook) }
|
||||
|
||||
describe '.recent' do
|
||||
let(:hook) { create(:project_hook) }
|
||||
let(:hook) { build(:project_hook) }
|
||||
|
||||
it 'does not return web hook logs that are too old' do
|
||||
create(:web_hook_log, web_hook: hook, created_at: 10.days.ago)
|
||||
|
|
@ -30,8 +30,10 @@ RSpec.describe WebHookLog do
|
|||
end
|
||||
|
||||
describe '#save' do
|
||||
let(:hook) { build(:project_hook) }
|
||||
|
||||
context 'with basic auth credentials' do
|
||||
let(:web_hook_log) { build(:web_hook_log, url: 'http://test:123@example.com') }
|
||||
let(:web_hook_log) { build(:web_hook_log, web_hook: hook, url: 'http://test:123@example.com') }
|
||||
|
||||
subject { web_hook_log.save! }
|
||||
|
||||
|
|
@ -45,9 +47,9 @@ RSpec.describe WebHookLog do
|
|||
end
|
||||
|
||||
context "with users' emails" do
|
||||
let(:author) { create(:user) }
|
||||
let(:user) { create(:user) }
|
||||
let(:web_hook_log) { create(:web_hook_log, request_data: data) }
|
||||
let(:author) { build(:user) }
|
||||
let(:user) { build(:user) }
|
||||
let(:web_hook_log) { create(:web_hook_log, web_hook: hook, request_data: data) }
|
||||
let(:data) do
|
||||
{
|
||||
user: {
|
||||
|
|
@ -93,11 +95,12 @@ RSpec.describe WebHookLog do
|
|||
end
|
||||
|
||||
describe '.delete_batch_for' do
|
||||
let(:hook) { create(:project_hook) }
|
||||
let_it_be(:hook) { build(:project_hook) }
|
||||
let_it_be(:hook2) { build(:project_hook) }
|
||||
|
||||
before do
|
||||
before_all do
|
||||
create_list(:web_hook_log, 3, web_hook: hook)
|
||||
create_list(:web_hook_log, 3)
|
||||
create_list(:web_hook_log, 3, web_hook: hook2)
|
||||
end
|
||||
|
||||
subject { described_class.delete_batch_for(hook, batch_size: batch_size) }
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ RSpec.describe WebHook do
|
|||
|
||||
describe '.web_hooks_disable_failed?' do
|
||||
it 'returns true when feature is enabled for parent' do
|
||||
second_hook = build(:project_hook, project: create(:project))
|
||||
second_hook = build(:project_hook)
|
||||
stub_feature_flags(web_hooks_disable_failed: [false, second_hook.project])
|
||||
|
||||
expect(described_class.web_hooks_disable_failed?(hook)).to eq(false)
|
||||
|
|
|
|||
|
|
@ -69,48 +69,6 @@ RSpec.describe API::Internal::Kubernetes do
|
|||
context 'is authenticated for an agent' do
|
||||
let!(:agent_token) { create(:cluster_agent_token) }
|
||||
|
||||
# Todo: Remove gitops_sync_count and k8s_api_proxy_request_count in the next milestone
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/369489
|
||||
# We're only keeping it for backwards compatibility until KAS is released
|
||||
# using `counts:` instead
|
||||
context 'deprecated events' do
|
||||
it 'returns no_content for valid events' do
|
||||
send_request(params: { gitops_sync_count: 10, k8s_api_proxy_request_count: 5 })
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
|
||||
it 'returns no_content for counts of zero' do
|
||||
send_request(params: { gitops_sync_count: 0, k8s_api_proxy_request_count: 0 })
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
end
|
||||
|
||||
it 'returns 400 for non number' do
|
||||
send_request(params: { gitops_sync_count: 'string', k8s_api_proxy_request_count: 1 })
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it 'returns 400 for negative number' do
|
||||
send_request(params: { gitops_sync_count: -1, k8s_api_proxy_request_count: 1 })
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it 'tracks events' do
|
||||
counters = { gitops_sync_count: 10, k8s_api_proxy_request_count: 5 }
|
||||
expected_counters = {
|
||||
kubernetes_agent_gitops_sync: counters[:gitops_sync_count],
|
||||
kubernetes_agent_k8s_api_proxy_request: counters[:k8s_api_proxy_request_count]
|
||||
}
|
||||
|
||||
send_request(params: counters)
|
||||
|
||||
expect(Gitlab::UsageDataCounters::KubernetesAgentCounter.totals).to eq(expected_counters)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns no_content for valid events' do
|
||||
counters = { gitops_sync: 10, k8s_api_proxy_request: 5 }
|
||||
unique_counters = { agent_users_using_ci_tunnel: [10] }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
RSpec.shared_examples 'includes Limitable concern' do
|
||||
describe '#exceeds_limits?' do
|
||||
let(:plan_limits) { create(:plan_limits, :default_plan) }
|
||||
let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
|
||||
|
||||
context 'without plan limits configured' do
|
||||
it { expect(subject.exceeds_limits?).to eq false }
|
||||
|
|
@ -26,7 +26,7 @@ RSpec.shared_examples 'includes Limitable concern' do
|
|||
end
|
||||
|
||||
describe 'validations' do
|
||||
let(:plan_limits) { create(:plan_limits, :default_plan) }
|
||||
let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
|
||||
|
||||
it { is_expected.to be_a(Limitable) }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue