Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-06 12:10:58 +00:00
parent b3c8b65ec2
commit f2c27c6f97
76 changed files with 1189 additions and 741 deletions

View File

@ -15,7 +15,6 @@
# SEED_NESTED_GROUPS: "false" # requires network connection
.run-dev-fixtures-script: &run-dev-fixtures-script
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- run_timed_command "RAILS_ENV=test bundle exec rake db:seed_fu"

View File

@ -103,7 +103,6 @@ update-yarn-cache:
WEBPACK_VENDOR_DLL: "true"
script:
- run_timed_command "gem install knapsack --no-document"
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source ./scripts/rspec_helpers.sh
- rspec_paralellized_job "--tag frontend_fixture"

View File

@ -10,7 +10,6 @@
# Only install knapsack after bundle install! Otherwise oddly some native
# gems could not be found under some circumstance. No idea why, hours wasted.
- run_timed_command "gem install knapsack --no-document"
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
- source ./scripts/rspec_helpers.sh
@ -150,20 +149,35 @@ setup-test-env:
script:
- run_timed_command "bundle exec ruby -I. -e 'require \"config/environment\"; TestEnv.init'"
- run_timed_command "scripts/gitaly-test-build" # Do not use 'bundle exec' here
- rm tmp/tests/gitaly/.ruby-bundle # This file prevents gems from being installed even if vendor/gitaly-ruby is missing
artifacts:
expire_in: 7d
paths:
- config/secrets.yml
- tmp/tests/gitaly
- tmp/tests/gitlab-elasticsearch-indexer
- tmp/tests/gitlab-shell
- tmp/tests/gitlab-test-fork
- tmp/tests/gitlab-test-fork_bare
- tmp/tests/gitlab-test
- tmp/tests/gitlab-workhorse
- tmp/tests/repositories
- tmp/tests/second_storage
- tmp/tests/gitaly/config.toml
- tmp/tests/gitaly/gitaly
- tmp/tests/gitaly/gitaly2.config.toml
- tmp/tests/gitaly/gitaly-git2go
- tmp/tests/gitaly/gitaly-hooks
- tmp/tests/gitaly/gitaly-lfs-smudge
- tmp/tests/gitaly/gitaly-ssh
- tmp/tests/gitaly/internal/
- tmp/tests/gitaly/internal_sockets/
- tmp/tests/gitaly/Makefile
- tmp/tests/gitaly/praefect
- tmp/tests/gitaly/praefect.config.toml
- tmp/tests/gitaly/ruby/
- tmp/tests/gitlab-elasticsearch-indexer/bin/gitlab-elasticsearch-indexer
- tmp/tests/gitlab-shell/
- tmp/tests/gitlab-test-fork/
- tmp/tests/gitlab-test-fork_bare/
- tmp/tests/gitlab-test/
- tmp/tests/gitlab-workhorse/gitlab-zip-metadata
- tmp/tests/gitlab-workhorse/gitlab-zip-cat
- tmp/tests/gitlab-workhorse/gitlab-workhorse
- tmp/tests/gitlab-workhorse/gitlab-resize-image
- tmp/tests/gitlab-workhorse/config.toml
- tmp/tests/repositories/
- tmp/tests/second_storage/
when: always
update-rails-cache:

View File

@ -166,12 +166,6 @@ Lint/MixedRegexpCaptureTypes:
- 'lib/gitlab/slash_commands/issue_new.rb'
- 'lib/gitlab/slash_commands/run.rb'
# Offense count: 1
# Cop supports --auto-correct.
Lint/NonDeterministicRequireOrder:
Exclude:
- 'rubocop/rubocop.rb'
# Offense count: 135
# Cop supports --auto-correct.
Lint/RedundantCopDisableDirective:

View File

@ -1 +1 @@
01e940cac5cdeaf1b14a18f3f71e0a3c50d1c60c
4cc3e803023c9178a2112ac3f6bcd5b6b8660fd1

View File

@ -22,7 +22,7 @@ gem 'rugged', '~> 0.28'
gem 'grape-path-helpers', '~> 1.5'
gem 'faraday', '~> 1.0'
gem 'marginalia', '~> 1.9.0'
gem 'marginalia', '~> 1.10.0'
# Authentication libraries
gem 'devise', '~> 4.7.2'

View File

@ -695,7 +695,7 @@ GEM
mini_mime (>= 0.1.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
marginalia (1.9.0)
marginalia (1.10.0)
actionpack (>= 2.3)
activerecord (>= 2.3)
memoist (0.16.2)
@ -1415,7 +1415,7 @@ DEPENDENCIES
loofah (~> 2.2)
lru_redux
mail (= 2.7.1)
marginalia (~> 1.9.0)
marginalia (~> 1.10.0)
memory_profiler (~> 0.9)
method_source (~> 1.0)
mimemagic (~> 0.3.2)

View File

@ -6,20 +6,11 @@ import alertsHelpUrlQuery from '../graphql/queries/alert_help_url.query.graphql'
export default {
i18n: {
emptyState: {
opsgenie: {
title: s__('AlertManagement|Opsgenie is enabled'),
info: s__(
'AlertManagement|You have enabled the Opsgenie integration. Your alerts will be visible directly in Opsgenie.',
),
buttonText: s__('AlertManagement|View alerts in Opsgenie'),
},
gitlab: {
title: s__('AlertManagement|Surface alerts in GitLab'),
info: s__(
'AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
),
buttonText: s__('AlertManagement|Authorize external service'),
},
title: s__('AlertManagement|Surface alerts in GitLab'),
info: s__(
'AlertManagement|Display alerts from all your monitoring tools directly within GitLab. Streamline the investigation of your alerts and the escalation of alerts to incidents.',
),
buttonText: s__('AlertManagement|Authorize external service'),
},
moreInformation: s__('AlertManagement|More information'),
},
@ -33,46 +24,27 @@ export default {
query: alertsHelpUrlQuery,
},
},
inject: [
'enableAlertManagementPath',
'userCanEnableAlertManagement',
'emptyAlertSvgPath',
'opsgenieMvcEnabled',
'opsgenieMvcTargetUrl',
],
inject: ['enableAlertManagementPath', 'userCanEnableAlertManagement', 'emptyAlertSvgPath'],
data() {
return {
alertsHelpUrl: '',
};
},
computed: {
emptyState() {
return {
...(this.opsgenieMvcEnabled
? this.$options.i18n.emptyState.opsgenie
: this.$options.i18n.emptyState.gitlab),
link: this.opsgenieMvcEnabled ? this.opsgenieMvcTargetUrl : this.enableAlertManagementPath,
};
},
alertsCanBeEnabled() {
return this.userCanEnableAlertManagement || this.opsgenieMvcEnabled;
},
},
};
</script>
<template>
<div>
<gl-empty-state :title="emptyState.title" :svg-path="emptyAlertSvgPath">
<gl-empty-state :title="$options.i18n.emptyState.title" :svg-path="emptyAlertSvgPath">
<template #description>
<div class="gl-display-block">
<span>{{ emptyState.info }}</span>
<gl-link v-if="!opsgenieMvcEnabled" :href="alertsHelpUrl" target="_blank">
<span>{{ $options.i18n.emptyState.info }}</span>
<gl-link :href="alertsHelpUrl" target="_blank">
{{ $options.i18n.moreInformation }}
</gl-link>
</div>
<div v-if="alertsCanBeEnabled" class="gl-display-block center gl-pt-4">
<gl-button category="primary" variant="success" :href="emptyState.link">
{{ emptyState.buttonText }}
<div v-if="userCanEnableAlertManagement" class="gl-display-block center gl-pt-4">
<gl-button category="primary" variant="success" :href="enableAlertManagementPath">
{{ $options.i18n.emptyState.buttonText }}
</gl-button>
</div>
</template>

View File

@ -17,12 +17,10 @@ export default () => {
emptyAlertSvgPath,
populatingAlertsHelpUrl,
alertsHelpUrl,
opsgenieMvcTargetUrl,
textQuery,
assigneeUsernameQuery,
alertManagementEnabled,
userCanEnableAlertManagement,
opsgenieMvcEnabled,
} = domEl.dataset;
const apolloProvider = new VueApollo({
@ -57,10 +55,8 @@ export default () => {
enableAlertManagementPath,
populatingAlertsHelpUrl,
emptyAlertSvgPath,
opsgenieMvcTargetUrl,
alertManagementEnabled: parseBoolean(alertManagementEnabled),
userCanEnableAlertManagement: parseBoolean(userCanEnableAlertManagement),
opsgenieMvcEnabled: parseBoolean(opsgenieMvcEnabled),
},
apolloProvider,
components: {

View File

@ -18,14 +18,11 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import MappingBuilder from './alert_mapping_builder.vue';
import AlertSettingsFormHelpBlock from './alert_settings_form_help_block.vue';
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
import service from '../services';
import {
integrationTypesNew,
integrationTypes,
JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder,
targetOpsgenieUrlPlaceholder,
typeSet,
sectionHash,
} from '../constants';
// Mocks will be removed when integrating with BE is ready
// data format is defined and will be the same as mocked (maybe with some minor changes)
@ -91,20 +88,13 @@ export const i18n = {
'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
),
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
opsgenie: {
label: s__('AlertSettings|2. Add link to your Opsgenie alert list'),
info: s__(
'AlertSettings|Utilizing this option will link the GitLab Alerts navigation item to your existing Opsgenie instance. By selecting this option, you cannot receive alerts from any other source in GitLab; it will effectively be turning Alerts within GitLab off as a feature.',
),
},
},
};
export default {
integrationTypes,
placeholders: {
prometheus: targetPrometheusUrlPlaceholder,
opsgenie: targetOpsgenieUrlPlaceholder,
},
JSON_VALIDATE_DELAY,
typeSet,
@ -134,10 +124,6 @@ export default {
prometheus: {
default: {},
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
opsgenie: {
default: {},
},
},
mixins: [glFeatureFlagsMixin()],
props: {
@ -149,12 +135,6 @@ export default {
type: Boolean,
required: true,
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
canManageOpsgenie: {
type: Boolean,
required: false,
default: false,
},
},
apollo: {
currentIntegration: {
@ -163,7 +143,7 @@ export default {
},
data() {
return {
selectedIntegration: integrationTypesNew[0].value,
selectedIntegration: integrationTypes[0].value,
active: false,
formVisible: false,
integrationTestPayload: {
@ -174,8 +154,6 @@ export default {
customMapping: null,
parsingPayload: false,
currentIntegration: null,
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
isManagingOpsgenie: false,
};
},
computed: {
@ -185,32 +163,12 @@ export default {
jsonIsValid() {
return this.integrationTestPayload.error === null;
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
disabledIntegrations() {
const options = [];
if (this.opsgenie.active) {
options.push(typeSet.http, typeSet.prometheus);
} else if (!this.canManageOpsgenie) {
options.push(typeSet.opsgenie);
}
return options;
},
options() {
return integrationTypesNew.map((el) => ({
...el,
disabled: this.disabledIntegrations.includes(el.value),
}));
},
selectedIntegrationType() {
switch (this.selectedIntegration) {
case typeSet.http:
return this.generic;
case typeSet.prometheus:
return this.prometheus;
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
case typeSet.opsgenie:
return this.opsgenie;
default:
return {};
}
@ -285,49 +243,17 @@ export default {
},
methods: {
integrationTypeSelect() {
if (this.selectedIntegration === integrationTypesNew[0].value) {
if (this.selectedIntegration === integrationTypes[0].value) {
this.formVisible = false;
} else {
this.formVisible = true;
}
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
if (this.canManageOpsgenie && this.selectedIntegration === typeSet.opsgenie) {
this.isManagingOpsgenie = true;
this.active = this.opsgenie.active;
this.integrationForm.apiUrl = this.opsgenie.opsgenieMvcTargetUrl;
} else {
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
this.isManagingOpsgenie = false;
}
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
submitWithOpsgenie() {
return service
.updateGenericActive({
endpoint: this.opsgenie.formPath,
params: {
service: {
opsgenie_mvc_target_url: this.integrationForm.apiUrl,
opsgenie_mvc_enabled: this.active,
},
},
})
.then(() => {
window.location.hash = sectionHash;
window.location.reload();
});
},
submitWithTestPayload() {
this.$emit('set-test-alert-payload', this.testAlertPayload);
this.submit();
},
submit() {
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
if (this.isManagingOpsgenie) {
return this.submitWithOpsgenie();
}
const { name, apiUrl } = this.integrationForm;
const variables =
this.selectedIntegration === typeSet.http
@ -343,7 +269,7 @@ export default {
return this.$emit('create-new-integration', integrationPayload);
},
reset() {
this.selectedIntegration = integrationTypesNew[0].value;
this.selectedIntegration = integrationTypes[0].value;
this.integrationTypeSelect();
if (this.currentIntegration) {
@ -360,9 +286,6 @@ export default {
error: null,
};
this.active = false;
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
this.isManagingOpsgenie = false;
},
resetAuthKey() {
if (!this.currentIntegration) {
@ -428,8 +351,8 @@ export default {
<gl-form-select
v-model="selectedIntegration"
:disabled="isSelectDisabled"
:class="{ 'gl-bg-gray-100!': isSelectDisabled }"
:options="options"
class="mw-100"
:options="$options.integrationTypes"
@change="integrationTypeSelect"
/>
@ -441,37 +364,7 @@ export default {
</div>
</gl-form-group>
<gl-collapse v-model="formVisible" class="gl-mt-3">
<!-- TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657 -->
<div v-if="isManagingOpsgenie">
<gl-form-group
id="integration-webhook"
:label="$options.i18n.integrationFormSteps.opsgenie.label"
label-for="integration-webhook"
>
<span class="gl-my-4">
{{ $options.i18n.integrationFormSteps.opsgenie.info }}
</span>
<gl-toggle
v-model="active"
:is-loading="loading"
:label="__('Active')"
class="gl-my-4 gl-font-weight-normal"
/>
<gl-form-input
id="opsgenie-opsgenieMvcTargetUrl"
v-model="integrationForm.apiUrl"
type="text"
:placeholder="$options.placeholders.opsgenie"
/>
<span class="gl-text-gray-400 gl-my-1">
{{ $options.i18n.integrationFormSteps.prometheusFormUrl.help }}
</span>
</gl-form-group>
</div>
<div v-else>
<div>
<gl-form-group
id="name-integration"
:label="$options.i18n.integrationFormSteps.step2.label"
@ -661,9 +554,7 @@ export default {
data-testid="integration-form-submit"
>{{ s__('AlertSettings|Save integration') }}
</gl-button>
<!-- TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657 -->
<gl-button
v-if="!isManagingOpsgenie"
data-testid="integration-test-and-submit"
:disabled="isSubmitTestPayloadDisabled"
category="secondary"
@ -672,12 +563,7 @@ export default {
@click="submitWithTestPayload"
>{{ s__('AlertSettings|Save and test payload') }}</gl-button
>
<gl-button
type="reset"
class="js-no-auto-disable"
:class="{ 'gl-ml-3': isManagingOpsgenie }"
>{{ __('Cancel') }}</gl-button
>
<gl-button type="reset" class="js-no-auto-disable">{{ __('Cancel') }}</gl-button>
</div>
</gl-collapse>
</gl-form>

View File

@ -1,5 +1,4 @@
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import { fetchPolicies } from '~/lib/graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
@ -41,10 +40,6 @@ export default {
),
},
components: {
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
GlAlert,
GlLink,
GlSprintf,
IntegrationsList,
AlertSettingsForm,
},
@ -55,10 +50,6 @@ export default {
prometheus: {
default: {},
},
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
opsgenie: {
default: {},
},
projectPath: {
default: '',
},
@ -105,13 +96,6 @@ export default {
canAddIntegration() {
return this.multiIntegrations || this.integrations?.list?.length < 2;
},
canManageOpsgenie() {
return (
this.opsgenie.active ||
this.integrations?.list?.every(({ active }) => active === false) ||
this.integrations?.list?.length === 0
);
},
},
methods: {
createNewIntegration({ type, variables }) {
@ -319,27 +303,7 @@ export default {
<template>
<div>
<!-- TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657 -->
<gl-alert v-if="opsgenie.active" :dismissible="false" variant="tip">
<gl-sprintf
:message="
s__(
'AlertSettings|We will soon be introducing the ability to create multiple unique HTTP endpoints. When this functionality is live, you will be able to configure an integration with Opsgenie to surface Opsgenie alerts in GitLab. This will replace the current Opsgenie integration which will be deprecated. %{linkStart}More Information%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link
class="gl-display-inline-block"
href="https://gitlab.com/gitlab-org/gitlab/-/issues/273657"
target="_blank"
>{{ content }}</gl-link
>
</template>
</gl-sprintf>
</gl-alert>
<integrations-list
v-else
:integrations="integrations.list"
:loading="loading"
@edit-integration="editIntegration"
@ -348,7 +312,6 @@ export default {
<alert-settings-form
:loading="isUpdating"
:can-add-integration="canAddIntegration"
:can-manage-opsgenie="canManageOpsgenie"
@create-new-integration="createNewIntegration"
@update-integration="updateIntegration"
@reset-token="resetToken"

View File

@ -40,22 +40,15 @@ export const i18n = {
integration: s__('AlertSettings|Integration'),
};
// TODO: Delete as part of old form removal in 13.6
export const integrationTypes = [
{ value: '', text: s__('AlertSettings|Select integration type') },
{ value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
{ value: 'PROMETHEUS', text: s__('AlertSettings|External Prometheus') },
{ value: 'OPSGENIE', text: s__('AlertSettings|Opsgenie') },
];
export const integrationTypesNew = [
{ value: '', text: s__('AlertSettings|Select integration type') },
...integrationTypes,
];
export const typeSet = {
http: 'HTTP',
prometheus: 'PROMETHEUS',
opsgenie: 'OPSGENIE',
};
export const integrationToDeleteDefault = { id: null, name: '' };
@ -63,7 +56,6 @@ export const integrationToDeleteDefault = { id: null, name: '' };
export const JSON_VALIDATE_DELAY = 250;
export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
export const targetOpsgenieUrlPlaceholder = 'https://app.opsgenie.com/alert/list/';
export const sectionHash = 'js-alert-management-settings';

View File

@ -29,10 +29,6 @@ export default (el) => {
formPath,
authorizationKey,
url,
opsgenieMvcAvailable,
opsgenieMvcFormPath,
opsgenieMvcEnabled,
opsgenieMvcTargetUrl,
projectPath,
multiIntegrations,
} = el.dataset;
@ -56,12 +52,6 @@ export default (el) => {
token: authorizationKey,
url,
},
opsgenie: {
formPath: opsgenieMvcFormPath,
active: parseBoolean(opsgenieMvcEnabled),
opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
},
projectPath,
multiIntegrations: parseBoolean(multiIntegrations),
},

View File

@ -23,7 +23,7 @@ export const KEEP_INFO_TEXT = s__(
export const KEEP_N_LABEL = s__('ContainerRegistry|Keep the most recent:');
export const NAME_REGEX_KEEP_LABEL = s__('ContainerRegistry|Keep tags matching:');
export const NAME_REGEX_KEEP_DESCRIPTION = s__(
'ContainerRegistry|Tags with names that match this regex pattern are kept. %{linkStart}More information%{linkEnd}',
'ContainerRegistry|Tags with names that match this regex pattern are kept. %{linkStart}View regex examples.%{linkEnd}',
);
export const REMOVE_HEADER_TEXT = s__('ContainerRegistry|Remove these tags');
@ -34,7 +34,7 @@ export const EXPIRATION_SCHEDULE_LABEL = s__('ContainerRegistry|Remove tags olde
export const NAME_REGEX_LABEL = s__('ContainerRegistry|Remove tags matching:');
export const NAME_REGEX_PLACEHOLDER = '.*';
export const NAME_REGEX_DESCRIPTION = s__(
'ContainerRegistry|Tags with names that match this regex pattern are removed. %{linkStart}More information%{linkEnd}',
'ContainerRegistry|Tags with names that match this regex pattern are removed. %{linkStart}View regex examples.%{linkEnd}',
);
export const ENABLED_TOGGLE_DESCRIPTION = s__(

View File

@ -1,14 +1,13 @@
<script>
// NOTE! For the first iteration, we are simply copying the implementation of Assignees
// It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { GlLoadingIcon } from '@gitlab/ui';
import { n__ } from '~/locale';
export default {
name: 'ReviewerTitle',
components: {
GlLoadingIcon,
GlIcon,
},
props: {
loading: {
@ -24,11 +23,6 @@ export default {
type: Boolean,
required: true,
},
showToggle: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
reviewerTitle() {
@ -52,14 +46,5 @@ export default {
>
{{ __('Edit') }}
</a>
<a
v-if="showToggle"
:aria-label="__('Toggle sidebar')"
class="gutter-toggle float-right js-sidebar-toggle"
href="#"
role="button"
>
<gl-icon data-hidden="true" name="chevron-double-lg-right" :size="12" />
</a>
</div>
</template>

View File

@ -26,11 +26,6 @@ export default {
type: String,
required: true,
},
signedIn: {
type: Boolean,
required: false,
default: false,
},
issuableType: {
type: String,
required: false,
@ -98,7 +93,6 @@ export default {
:number-of-reviewers="store.reviewers.length"
:loading="loading || store.isFetching.reviewers"
:editable="store.editable"
:show-toggle="!signedIn"
/>
<reviewers
v-if="!store.isFetching.reviewers"

View File

@ -77,7 +77,6 @@ function mountReviewersComponent(mediator) {
issuableIid: String(iid),
projectPath: fullPath,
field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'),
issuableType: isInIssuePage() ? 'issue' : 'merge_request',
},
}),

View File

@ -1,19 +1,36 @@
<script>
/* eslint-disable vue/no-v-html */
import { GlButton } from '@gitlab/ui';
import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
import { escape } from 'lodash';
import { __, sprintf } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon.vue';
import rebaseQuery from '../../queries/states/ready_to_merge.query.graphql';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import { deprecatedCreateFlash as Flash } from '../../../flash';
import { __, sprintf } from '~/locale';
export default {
name: 'MRWidgetRebase',
apollo: {
state: {
query: rebaseQuery,
skip() {
return !this.glFeatures.mergeRequestWidgetGraphql;
},
variables() {
return this.mergeRequestQueryVariables;
},
update: (data) => data.project.mergeRequest,
},
},
components: {
statusIcon,
GlButton,
GlSkeletonLoader,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: {
mr: {
type: Object,
@ -26,16 +43,41 @@ export default {
},
data() {
return {
state: {},
isMakingRequest: false,
rebasingError: null,
};
},
computed: {
isLoading() {
return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading;
},
rebaseInProgress() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.rebaseInProgress;
}
return this.mr.rebaseInProgress;
},
canPushToSourceBranch() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.userPermissions.pushToSourceBranch;
}
return this.mr.canPushToSourceBranch;
},
targetBranch() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.targetBranch;
}
return this.mr.targetBranch;
},
status() {
if (this.mr.rebaseInProgress || this.isMakingRequest) {
if (this.rebaseInProgress || this.isMakingRequest) {
return 'loading';
}
if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) {
if (!this.canPushToSourceBranch && !this.rebaseInProgress) {
return 'warning';
}
return 'success';
@ -49,7 +91,7 @@ export default {
'Fast-forward merge is not possible. Rebase the source branch onto %{targetBranch} to allow this merge request to be merged.',
),
{
targetBranch: `<span class="label-branch">${escape(this.mr.targetBranch)}</span>`,
targetBranch: `<span class="label-branch">${escape(this.targetBranch)}</span>`,
},
false,
);
@ -105,17 +147,30 @@ export default {
</script>
<template>
<div class="mr-widget-body media">
<status-icon :status="status" :show-disabled-button="showDisabledButton" />
<div v-if="isLoading" class="gl-w-full mr-conflict-loader">
<gl-skeleton-loader :width="334" :height="30">
<rect x="0" y="3" width="24" height="24" rx="4" />
<rect x="32" y="5" width="302" height="20" rx="4" />
</gl-skeleton-loader>
</div>
<template v-else>
<status-icon :status="status" :show-disabled-button="showDisabledButton" />
<div class="rebase-state-find-class-convention media media-body space-children">
<template v-if="mr.rebaseInProgress || isMakingRequest">
<span class="bold" data-testid="rebase-message">{{ __('Rebase in progress') }}</span>
</template>
<template v-if="!mr.rebaseInProgress && !mr.canPushToSourceBranch">
<span class="bold" data-testid="rebase-message" v-html="fastForwardMergeText"></span>
</template>
<template v-if="!mr.rebaseInProgress && mr.canPushToSourceBranch && !isMakingRequest">
<div class="rebase-state-find-class-convention media media-body space-children">
<span
v-if="rebaseInProgress || isMakingRequest"
class="gl-font-weight-bold"
data-testid="rebase-message"
>{{ __('Rebase in progress') }}</span
>
<span
v-if="!rebaseInProgress && !canPushToSourceBranch"
class="gl-font-weight-bold"
data-testid="rebase-message"
v-html="fastForwardMergeText"
></span>
<div
v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
class="accept-merge-holder clearfix js-toggle-container accept-action media space-children"
>
<gl-button
@ -126,14 +181,16 @@ export default {
>
{{ __('Rebase') }}
</gl-button>
<span v-if="!rebasingError" class="bold" data-testid="rebase-message">{{
<span v-if="!rebasingError" class="gl-font-weight-bold" data-testid="rebase-message">{{
__(
'Fast-forward merge is not possible. Rebase the source branch onto the target branch.',
)
}}</span>
<span v-else class="bold danger" data-testid="rebase-message">{{ rebasingError }}</span>
<span v-else class="gl-font-weight-bold danger" data-testid="rebase-message">{{
rebasingError
}}</span>
</div>
</template>
</div>
</div>
</template>
</div>
</template>

View File

@ -0,0 +1,11 @@
query rebaseQuery($projectPath: ID!, $iid: String!) {
project(fullPath: $projectPath) {
mergeRequest(iid: $iid) {
rebaseInProgress
targetBranch
userPermissions {
pushToSourceBranch
}
}
}
}

View File

@ -10,7 +10,6 @@
@import './pages/events';
@import './pages/groups';
@import './pages/help';
@import './pages/incident_management_list';
@import './pages/issuable';
@import './pages/issues/issue_count_badge';
@import './pages/issues';

View File

@ -588,6 +588,12 @@ table.code {
// Merge request diff grid layout
.diff-grid {
.diff-td {
// By default min-width is auto with 1fr which causes some overflow problems
// https://gitlab.com/gitlab-org/gitlab/-/issues/296222
min-width: 0;
}
.diff-grid-row {
display: grid;
grid-template-columns: 1fr 1fr;
@ -601,27 +607,27 @@ table.code {
.diff-grid-comments {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
grid-template-columns: 1fr 1fr;
}
.diff-grid-drafts {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
grid-template-columns: 1fr 1fr;
}
&.inline {
.diff-grid-comments {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-columns: 1fr;
}
.diff-grid-drafts {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-columns: 1fr;
}
.diff-grid-row {
grid-template-columns: minmax(0, 1fr);
grid-template-columns: 1fr;
}
.diff-grid-left,

View File

@ -1,11 +1,13 @@
@import 'mixins_and_variables_and_functions';
.incident-management-list {
.new-alert {
background-color: $issues-today-bg;
background-color: var(--green-50, $green-50);
}
// these styles need to be deleted once GlTable component looks in GitLab same as in @gitlab/ui
table {
@include gl-text-gray-500;
color: var(--gray-500, $gray-500);
tbody {
tr:not(.b-table-busy-slot):not(.b-table-empty-row) {
@ -34,7 +36,8 @@
th {
@include gl-bg-transparent;
@include gl-font-weight-bold;
@include gl-text-gray-400;
color: var(--gray-400, $gray-400);
&[aria-sort='none']:hover {
background-image: url('data:image/svg+xml, %3csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="4 0 8 16"%3e %3cpath style="fill: %23BABABA;" fill-rule="evenodd" d="M11.707085,11.7071 L7.999975,15.4142 L4.292875,11.7071 C3.902375,11.3166 3.902375, 10.6834 4.292875,10.2929 C4.683375,9.90237 5.316575,9.90237 5.707075,10.2929 L6.999975, 11.5858 L6.999975,2 C6.999975,1.44771 7.447695,1 7.999975,1 C8.552255,1 8.999975,1.44771 8.999975,2 L8.999975,11.5858 L10.292865,10.2929 C10.683395 ,9.90237 11.316555,9.90237 11.707085,10.2929 C12.097605,10.6834 12.097605,11.3166 11.707085,11.7071 Z"/%3e %3c/svg%3e');
@ -67,7 +70,7 @@
}
&:hover {
@include gl-bg-white;
background-color: var(--white, $white);
@include gl-border-none;
}
@ -80,7 +83,7 @@
&.alert-management-table {
.table-col {
&:last-child {
@include gl-bg-gray-10;
background-color: var(--gray-10, $gray-10);
&::before {
content: none !important;
@ -120,12 +123,12 @@
@include gl-border-b-0;
.gl-tab-nav-item {
@include gl-text-gray-500;
color: var(--gray-500, $gray-500);
> .gl-tab-counter-badge {
@include gl-reset-color;
@include gl-font-sm;
@include gl-bg-gray-50;
background-color: var(--gray-50, $gray-50);
}
}
}

View File

@ -301,7 +301,8 @@ $mr-widget-min-height: 69px;
margin: 0 0 0 10px;
}
.bold {
.bold,
.gl-font-weight-bold {
font-weight: $gl-font-weight-bold;
color: $gray-600;
margin-left: 10px;
@ -317,7 +318,8 @@ $mr-widget-min-height: 69px;
}
.spacing,
.bold {
.bold,
.gl-font-weight-bold {
vertical-align: middle;
}

View File

@ -1163,8 +1163,8 @@ input {
line-height: 18px;
margin: 4px 0 4px 2px;
}
.title-container .badge.badge-pill,
.navbar-nav .badge.badge-pill {
.title-container .badge.badge-pill:not(.merge-request-badge),
.navbar-nav .badge.badge-pill:not(.merge-request-badge) {
position: inherit;
font-weight: 400;
margin-left: -6px;

View File

@ -65,6 +65,7 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
}
modules.merge!(build_information_module)
modules.merge!(deployment_information_module)
modules
end
@ -73,17 +74,33 @@ class JiraConnect::AppDescriptorController < JiraConnect::ApplicationController
view_context.image_url('gitlab_logo.png')
end
# See: https://developer.atlassian.com/cloud/jira/software/modules/deployment/
def deployment_information_module
{
jiraDeploymentInfoProvider: common_module_properties.merge(
actions: {}, # TODO: list deployments
name: { value: "GitLab Deployments" },
key: "gitlab-deployments"
)
}
end
# See: https://developer.atlassian.com/cloud/jira/software/modules/build/
def build_information_module
{
jiraBuildInfoProvider: {
homeUrl: HOME_URL,
logoUrl: logo_url,
documentationUrl: DOC_URL,
jiraBuildInfoProvider: common_module_properties.merge(
actions: {},
name: { value: "GitLab CI" },
key: "gitlab-ci"
}
)
}
end
def common_module_properties
{
homeUrl: HOME_URL,
logoUrl: logo_url,
documentationUrl: DOC_URL
}
end

View File

@ -34,11 +34,7 @@ module Ci
pipelines =
if merge_request.persisted?
if Feature.enabled?(:ci_pipelines_for_merge_request_finder_new_cte, target_project)
pipelines_using_cte
else
pipelines_using_legacy_cte
end
pipelines_using_cte
else
triggered_for_branch.for_sha(commit_shas)
end
@ -49,18 +45,6 @@ module Ci
private
def pipelines_using_legacy_cte
cte = Gitlab::SQL::CTE.new(:shas, merge_request.all_commits.select(:sha))
source_sha_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha])
merged_result_pipelines = filter_by(triggered_by_merge_request, cte, source_sha_join)
detached_merge_request_pipelines = filter_by_sha(triggered_by_merge_request, cte)
pipelines_for_branch = filter_by_sha(triggered_for_branch, cte)
Ci::Pipeline.with(cte.to_arel) # rubocop: disable CodeReuse/ActiveRecord
.from_union([merged_result_pipelines, detached_merge_request_pipelines, pipelines_for_branch])
end
def pipelines_using_cte
cte = Gitlab::SQL::CTE.new(:shas, merge_request.all_commits.select(:sha))

View File

@ -109,6 +109,23 @@ class Deployment < ApplicationRecord
Deployments::ExecuteHooksWorker.perform_async(id)
end
end
after_transition any => any - [:skipped] do |deployment, transition|
next if transition.loopback?
next unless Feature.enabled?(:jira_sync_deployments, deployment.project)
deployment.run_after_commit do
::JiraConnect::SyncDeploymentsWorker.perform_async(id)
end
end
end
after_create unless: :importing? do |deployment|
next unless Feature.enabled?(:jira_sync_deployments, deployment.project)
run_after_commit do
::JiraConnect::SyncDeploymentsWorker.perform_async(deployment.id)
end
end
enum status: {

View File

@ -1,3 +1,4 @@
- page_title _('Alerts')
- add_page_specific_style 'page_bundles/incident_management_list'
#js-alert_management{ data: alert_management_data(@current_user, @project) }

View File

@ -1,3 +1,4 @@
- page_title _('Incidents')
- add_page_specific_style 'page_bundles/incident_management_list'
#js-incidents{ data: incidents_data(@project, params) }

View File

@ -71,7 +71,7 @@
= expanded ? _('Collapse') : _('Expand')
%p
= _("Save space and find images in the Container Registry. Remove unneeded tags and keep only the ones you want.")
= link_to _('More information'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy', target: '_blank', rel: 'noopener noreferrer')
= link_to _('How does cleanup work?'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy', target: '_blank', rel: 'noopener noreferrer')
.settings-content
= render 'projects/registry/settings/index'

View File

@ -1,6 +1,7 @@
- return unless can?(current_user, :admin_operations, @project)
- expanded = expanded_by_default?
- add_page_specific_style 'page_bundles/alert_management_settings'
- add_page_specific_style 'page_bundles/incident_management_list'
%section.settings.no-animate#js-alert-management-settings{ class: ('expanded' if expanded) }
.settings-header

View File

@ -891,6 +891,14 @@
:weight: 1
:idempotent: true
:tags: []
- :name: jira_connect:jira_connect_sync_deployments
:feature_category: :integrations
:has_external_dependencies: true
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: jira_connect:jira_connect_sync_merge_request
:feature_category: :integrations
:has_external_dependencies: true

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
module JiraConnect
class SyncDeploymentsWorker
include ApplicationWorker
idempotent!
worker_has_external_dependencies!
queue_namespace :jira_connect
feature_category :integrations
def perform(deployment_id, sequence_id)
deployment = Deployment.find_by_id(deployment_id)
return unless deployment
return unless Feature.enabled?(:jira_sync_deployments, deployment.project)
::JiraConnect::SyncService
.new(deployment.project)
.execute(deployments: [deployment], update_sequence_id: sequence_id)
end
def self.perform_async(id)
seq_id = ::Atlassian::JiraConnect::Client.generate_update_sequence_id
super(id, seq_id)
end
end
end

View File

@ -0,0 +1,6 @@
---
title: 'Refactor(opsgenie): remove Opsgenie integration frontend code from Incident
management'
merge_request: 50525
author:
type: deprecated

View File

@ -0,0 +1,5 @@
---
title: Fix duplicated toggle button showing on right sidebar when signed out
merge_request: 50892
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Improve the database query performance on the pipeline loading in merge requests
merge_request: 50818
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Updated link text to match style guidelines
merge_request: 50555
author:
type: other

View File

@ -177,6 +177,7 @@ module Gitlab
config.assets.precompile << "mailers/*.css"
config.assets.precompile << "page_bundles/_mixins_and_variables_and_functions.css"
config.assets.precompile << "page_bundles/alert_management_details.css"
config.assets.precompile << "page_bundles/alert_management_settings.css"
config.assets.precompile << "page_bundles/boards.css"
config.assets.precompile << "page_bundles/build.css"
config.assets.precompile << "page_bundles/ci_status.css"
@ -186,29 +187,29 @@ module Gitlab
config.assets.precompile << "page_bundles/epics.css"
config.assets.precompile << "page_bundles/error_tracking_details.css"
config.assets.precompile << "page_bundles/error_tracking_index.css"
config.assets.precompile << "page_bundles/signup.css"
config.assets.precompile << "page_bundles/ide.css"
config.assets.precompile << "page_bundles/import.css"
config.assets.precompile << "page_bundles/incident_management_list.css"
config.assets.precompile << "page_bundles/issues_list.css"
config.assets.precompile << "page_bundles/jira_connect.css"
config.assets.precompile << "page_bundles/jira_connect_users.css"
config.assets.precompile << "page_bundles/merge_conflicts.css"
config.assets.precompile << "page_bundles/merge_requests.css"
config.assets.precompile << "page_bundles/milestone.css"
config.assets.precompile << "page_bundles/oncall_schedules.css"
config.assets.precompile << "page_bundles/pipeline.css"
config.assets.precompile << "page_bundles/pipelines.css"
config.assets.precompile << "page_bundles/pipeline_schedules.css"
config.assets.precompile << "page_bundles/pipelines.css"
config.assets.precompile << "page_bundles/productivity_analytics.css"
config.assets.precompile << "page_bundles/profile_two_factor_auth.css"
config.assets.precompile << "page_bundles/security_dashboard.css"
config.assets.precompile << "page_bundles/terminal.css"
config.assets.precompile << "page_bundles/todos.css"
config.assets.precompile << "page_bundles/reports.css"
config.assets.precompile << "page_bundles/roadmap.css"
config.assets.precompile << "page_bundles/security_dashboard.css"
config.assets.precompile << "page_bundles/signup.css"
config.assets.precompile << "page_bundles/terminal.css"
config.assets.precompile << "page_bundles/todos.css"
config.assets.precompile << "page_bundles/wiki.css"
config.assets.precompile << "page_bundles/xterm.css"
config.assets.precompile << "page_bundles/alert_management_settings.css"
config.assets.precompile << "page_bundles/oncall_schedules.css"
config.assets.precompile << "lazy_bundles/cropper.css"
config.assets.precompile << "lazy_bundles/select2.css"
config.assets.precompile << "performance_bar.css"

View File

@ -1,8 +1,8 @@
---
name: ci_pipelines_for_merge_request_finder_new_cte
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49083
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/291006
name: jira_sync_deployments
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49757
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/294034
milestone: '13.7'
type: development
group: group::continuous integration
group: group::ecosystem
default_enabled: false

View File

@ -477,7 +477,7 @@ production: &base
ee_cron_jobs:
# Schedule snapshots for all devops adoption segments
analytics_devops_adoption_create_all_snapshots_worker:
cron: 0 0 1 * *
cron: 0 4 * * *
# Snapshot active users statistics
historical_data_worker:

View File

@ -543,7 +543,7 @@ Settings.cron_jobs['manage_evidence_worker']['job_class'] = 'Releases::ManageEvi
Gitlab.ee do
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker']['cron'] ||= '0 0 1 * *'
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker']['cron'] ||= '0 4 * * *'
Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker']['job_class'] = 'Analytics::DevopsAdoption::CreateAllSnapshotsWorker'
Settings.cron_jobs['active_user_count_threshold_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['active_user_count_threshold_worker']['cron'] ||= '0 12 * * *'

View File

@ -60,7 +60,7 @@ Reference pipeline: <https://gitlab.com/gitlab-org/gitlab/pipelines/135236627>
```mermaid
graph LR
subgraph "No needed jobs";
1-1["danger-review (3.5 minutes)"];
1-1["danger-review (2.3 minutes)"];
click 1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8100542&udv=0"
1-50["docs lint (9 minutes)"];
click 1-50 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356757&udv=0"
@ -76,23 +76,23 @@ graph RL;
classDef criticalPath fill:#f66;
subgraph "No needed jobs";
1-1["danger-review (3.5 minutes)"];
1-1["danger-review (2.3 minutes)"];
click 1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8100542&udv=0"
1-2["build-qa-image (2.4 minutes)"];
1-2["build-qa-image (1.6 minutes)"];
click 1-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914325&udv=0"
1-3["compile-test-assets (8.5 minutes)"];
1-3["compile-test-assets (7 minutes)"];
click 1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914317&udv=0"
1-4["compile-test-assets as-if-foss (8.35 minutes)"];
1-4["compile-test-assets as-if-foss (7 minutes)"];
click 1-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356616&udv=0"
1-5["compile-production-assets (19 minutes)"];
click 1-5 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914312&udv=0"
1-6["setup-test-env (7.4 minutes)"];
1-6["setup-test-env (9 minutes)"];
click 1-6 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914315&udv=0"
1-7["review-stop-failed-deployment"];
1-8["dependency_scanning"];
1-9["qa:internal, qa:internal-as-if-foss"];
1-11["qa:selectors, qa:selectors-as-if-foss"];
1-14["retrieve-tests-metadata (1.9 minutes)"];
1-14["retrieve-tests-metadata (1 minutes)"];
click 1-14 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356697&udv=0"
1-15["code_quality"];
1-16["brakeman-sast"];
@ -100,7 +100,7 @@ graph RL;
1-18["kubesec-sast"];
1-19["nodejs-scan-sast"];
1-20["secrets-sast"];
1-21["static-analysis (17 minutes)"];
1-21["static-analysis (30 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"
class 1-3 criticalPath;
@ -111,26 +111,26 @@ graph RL;
click 2_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356715&udv=0"
2_1-2["memory-static (4.75 minutes)"];
click 2_1-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356721&udv=0"
2_1-3["run-dev-fixtures (5 minutes)"];
2_1-3["run-dev-fixtures (6 minutes)"];
click 2_1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356729&udv=0"
2_1-4["run-dev-fixtures-ee (5 minutes)"];
2_1-4["run-dev-fixtures-ee (6.75 minutes)"];
click 2_1-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356731&udv=0"
subgraph "Needs `setup-test-env`";
2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6;
end
2_2-2["frontend-fixtures (16.5 minutes)"];
2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (12 minutes)"];
class 2_2-2 criticalPath;
click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0"
2_2-4["memory-on-boot (7.19 minutes)"];
2_2-4["memory-on-boot (6 minutes)"];
click 2_2-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356727&udv=0"
2_2-5["webpack-dev-server (6.1 minutes)"];
2_2-5["webpack-dev-server (4.5 minutes)"];
click 2_2-5 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8404303&udv=0"
subgraph "Needs `setup-test-env` & `compile-test-assets`";
2_2-2 & 2_2-4 & 2_2-5 --> 1-6 & 1-3;
end
2_3-1["build-assets-image (2.5 minutes)"];
2_3-1["build-assets-image (1.6 minutes)"];
subgraph "Needs `compile-production-assets`";
2_3-1 --> 1-5
end
@ -153,17 +153,17 @@ graph RL;
click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0"
3_1-2["karma (4 minutes)"];
click 3_1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914200&udv=0"
subgraph "Needs `frontend-fixtures`";
subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`";
3_1-1 & 3_1-2 --> 2_2-2;
end
3_2-1["rspec:coverage (7.5 minutes)"];
3_2-1["rspec:coverage (4.6 minutes)"];
subgraph "Depends on `rspec` jobs";
3_2-1 -.->|"(don't use needs because of limitations)"| 2_5-1;
click 3_2-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7248745&udv=0"
end
4_1-1["coverage-frontend (3.6 minutes)"];
4_1-1["coverage-frontend (2.75 minutes)"];
subgraph "Needs `jest`";
4_1-1 --> 3_1-1;
class 4_1-1 criticalPath;
@ -180,23 +180,23 @@ graph RL;
classDef criticalPath fill:#f66;
subgraph "No needed jobs";
1-1["danger-review (3.5 minutes)"];
1-1["danger-review (2.3 minutes)"];
click 1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8100542&udv=0"
1-2["build-qa-image (2.4 minutes)"];
1-2["build-qa-image (1.6 minutes)"];
click 1-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914325&udv=0"
1-3["compile-test-assets (8.5 minutes)"];
1-3["compile-test-assets (7 minutes)"];
click 1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914317&udv=0"
1-4["compile-test-assets as-if-foss (8.35 minutes)"];
1-4["compile-test-assets as-if-foss (7 minutes)"];
click 1-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356616&udv=0"
1-5["compile-production-assets (19 minutes)"];
click 1-5 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914312&udv=0"
1-6["setup-test-env (7.4 minutes)"];
1-6["setup-test-env (9 minutes)"];
click 1-6 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914315&udv=0"
1-7["review-stop-failed-deployment"];
1-8["dependency_scanning"];
1-9["qa:internal, qa:internal-as-if-foss"];
1-11["qa:selectors, qa:selectors-as-if-foss"];
1-14["retrieve-tests-metadata (1.9 minutes)"];
1-14["retrieve-tests-metadata (1 minutes)"];
click 1-14 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356697&udv=0"
1-15["code_quality"];
1-16["brakeman-sast"];
@ -204,7 +204,7 @@ graph RL;
1-18["kubesec-sast"];
1-19["nodejs-scan-sast"];
1-20["secrets-sast"];
1-21["static-analysis (17 minutes)"];
1-21["static-analysis (30 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"
class 1-3 criticalPath;
@ -216,26 +216,26 @@ graph RL;
click 2_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356715&udv=0"
2_1-2["memory-static (4.75 minutes)"];
click 2_1-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356721&udv=0"
2_1-3["run-dev-fixtures (5 minutes)"];
2_1-3["run-dev-fixtures (6 minutes)"];
click 2_1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356729&udv=0"
2_1-4["run-dev-fixtures-ee (5 minutes)"];
2_1-4["run-dev-fixtures-ee (6.75 minutes)"];
click 2_1-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356731&udv=0"
subgraph "Needs `setup-test-env`";
2_1-1 & 2_1-2 & 2_1-3 & 2_1-4 --> 1-6;
end
2_2-2["frontend-fixtures (16.5 minutes)"];
2_2-2["rspec frontend_fixture/rspec-ee frontend_fixture (12 minutes)"];
class 2_2-2 criticalPath;
click 2_2-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7910143&udv=0"
2_2-4["memory-on-boot (7.19 minutes)"];
2_2-4["memory-on-boot (6 minutes)"];
click 2_2-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356727&udv=0"
2_2-5["webpack-dev-server (6.1 minutes)"];
2_2-5["webpack-dev-server (4.5 minutes)"];
click 2_2-5 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8404303&udv=0"
subgraph "Needs `setup-test-env` & `compile-test-assets`";
2_2-2 & 2_2-4 & 2_2-5 --> 1-6 & 1-3;
end
2_3-1["build-assets-image (2.5 minutes)"];
2_3-1["build-assets-image (1.6 minutes)"];
class 2_3-1 criticalPath;
subgraph "Needs `compile-production-assets`";
2_3-1 --> 1-5
@ -266,17 +266,17 @@ graph RL;
click 3_1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914204&udv=0"
3_1-2["karma (4 minutes)"];
click 3_1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914200&udv=0"
subgraph "Needs `frontend-fixtures`";
subgraph "Needs `rspec frontend_fixture/rspec-ee frontend_fixture`";
3_1-1 & 3_1-2 --> 2_2-2;
end
3_2-1["rspec:coverage (7.5 minutes)"];
3_2-1["rspec:coverage (4.6 minutes)"];
subgraph "Depends on `rspec` jobs";
3_2-1 -.->|"(don't use needs because of limitations)"| 2_5-1;
click 3_2-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=7248745&udv=0"
end
4_1-1["coverage-frontend (3.6 minutes)"];
4_1-1["coverage-frontend (2.75 minutes)"];
subgraph "Needs `jest`";
4_1-1 --> 3_1-1;
class 4_1-1 criticalPath;
@ -311,23 +311,23 @@ graph RL;
classDef criticalPath fill:#f66;
subgraph "No needed jobs";
1-1["danger-review (3.5 minutes)"];
1-1["danger-review (2.3 minutes)"];
click 1-1 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8100542&udv=0"
1-2["build-qa-image (2.4 minutes)"];
1-2["build-qa-image (1.6 minutes)"];
click 1-2 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914325&udv=0"
1-3["compile-test-assets (8.5 minutes)"];
1-3["compile-test-assets (7 minutes)"];
click 1-3 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914317&udv=0"
1-4["compile-test-assets as-if-foss (8.35 minutes)"];
1-4["compile-test-assets as-if-foss (7 minutes)"];
click 1-4 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356616&udv=0"
1-5["compile-production-assets (19 minutes)"];
click 1-5 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914312&udv=0"
1-6["setup-test-env (7.4 minutes)"];
1-6["setup-test-env (9 minutes)"];
click 1-6 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914315&udv=0"
1-7["review-stop-failed-deployment"];
1-8["dependency_scanning"];
1-9["qa:internal, qa:internal-as-if-foss"];
1-11["qa:selectors, qa:selectors-as-if-foss"];
1-14["retrieve-tests-metadata (1.9 minutes)"];
1-14["retrieve-tests-metadata (1 minutes)"];
click 1-14 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=8356697&udv=0"
1-15["code_quality"];
1-16["brakeman-sast"];
@ -335,7 +335,7 @@ graph RL;
1-18["kubesec-sast"];
1-19["nodejs-scan-sast"];
1-20["secrets-sast"];
1-21["static-analysis (17 minutes)"];
1-21["static-analysis (30 minutes)"];
click 1-21 "https://app.periscopedata.com/app/gitlab/652085/Engineering-Productivity---Pipeline-Build-Durations?widget=6914471&udv=0"
class 1-5 criticalPath;
@ -347,13 +347,13 @@ graph RL;
2_1-1 --> 1-6;
end
2_3-1["build-assets-image (2.5 minutes)"];
2_3-1["build-assets-image (1.6 minutes)"];
subgraph "Needs `compile-production-assets`";
2_3-1 --> 1-5
class 2_3-1 criticalPath;
end
2_4-1["package-and-qa (108 minutes)"];
2_4-1["package-and-qa (105 minutes)"];
subgraph "Needs `build-qa-image` & `build-assets-image`";
2_4-1 --> 1-2 & 2_3-1;
class 2_4-1 criticalPath;
@ -504,6 +504,10 @@ request, be sure to start the `dont-interrupt-me` job before pushing.
- `update-yarn-cache`, defined in [`.gitlab/ci/frontend.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab/ci/frontend.gitlab-ci.yml).
1. These jobs run in merge requests whose title include `UPDATE CACHE`.
### Artifacts strategy
We limit the artifacts that are saved and retrieved by jobs to the minimum in order to reduce the upload/download time and costs, as well as the artifacts storage.
### Pre-clone step
The `gitlab-org/gitlab` project on GitLab.com uses a [pre-clone step](https://gitlab.com/gitlab-org/gitlab/-/issues/39134)

View File

@ -168,7 +168,7 @@ If the existing alert is already `resolved`, GitLab creates a new alert instead.
![Alert Management List](img/alert_list_v13_1.png)
## Link to your Opsgenie Alerts
## Link to your Opsgenie Alerts (depreciated)
WARNING:
We are building deeper integration with Opsgenie and other alerting tools through
@ -176,6 +176,10 @@ We are building deeper integration with Opsgenie and other alerting tools throug
the GitLab interface. As a result, the previous direct link to Opsgenie Alerts from
the GitLab alerts list is scheduled for deprecation following the 13.7 release on December 22, 2020.
NOTE:
This feature has been deprecated in
[13.8](https://gitlab.com/gitlab-org/gitlab/-/issues/273657).
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
You can monitor alerts using a GitLab integration with [Opsgenie](https://www.atlassian.com/software/opsgenie).

View File

@ -50,7 +50,9 @@ The DevOps Adoption tab shows you which segments of your organization are using
- Deploys
- Scanning
Segments are arbitrary collections of GitLab groups that you define. You might define a segment to represent a small team, a large department, or a whole organization. You are limited to creating a maximum of 20 segments, and each segment is limited to a maximum of 20 groups. Buttons to manage your segments appear in the DevOps Adoption section of the page.
Segments are arbitrary collections of GitLab groups that you define. You might define a segment to represent a small team, a large department, or a whole organization.
You are limited to creating a maximum of 20 segments, and each segment is limited to a maximum of 20 groups.
Buttons to manage your segments appear in the DevOps Adoption section of the page.
DevOps Adoption allows you to:

View File

@ -16,11 +16,13 @@ module Atlassian
common = { project: project, update_sequence_id: update_sequence_id }
dev_info = args.slice(:commits, :branches, :merge_requests)
build_info = args.slice(:pipelines)
deploy_info = args.slice(:deployments)
responses = []
responses << store_dev_info(**common, **dev_info) if dev_info.present?
responses << store_build_info(**common, **build_info) if build_info.present?
responses << store_deploy_info(**common, **deploy_info) if deploy_info.present?
raise ArgumentError, 'Invalid arguments' if responses.empty?
responses.compact
@ -28,6 +30,17 @@ module Atlassian
private
def store_deploy_info(project:, deployments:, **opts)
return unless Feature.enabled?(:jira_sync_deployments, project)
items = deployments.map { |d| Serializers::DeploymentEntity.represent(d, opts) }
items.reject! { |d| d.issue_keys.empty? }
return if items.empty?
post('/rest/deployments/0.1/bulk', { deployments: items })
end
def store_build_info(project:, pipelines:, update_sequence_id: nil)
return unless Feature.enabled?(:jira_sync_builds, project)

View File

@ -25,8 +25,10 @@ module Atlassian
# extract Jira issue keys from either the source branch/ref or the
# merge request title.
@issue_keys ||= begin
src = "#{pipeline.source_ref} #{pipeline.merge_request&.title}"
JiraIssueKeyExtractor.new(src).issue_keys
pipeline.all_merge_requests.flat_map do |mr|
src = "#{mr.source_branch} #{mr.title}"
JiraIssueKeyExtractor.new(src).issue_keys
end.uniq
end
end

View File

@ -0,0 +1,90 @@
# frozen_string_literal: true
module Atlassian
module JiraConnect
module Serializers
class DeploymentEntity < Grape::Entity
include Gitlab::Routing
format_with(:iso8601, &:iso8601)
expose :schema_version, as: :schemaVersion
expose :iid, as: :deploymentSequenceNumber
expose :update_sequence_id, as: :updateSequenceNumber
expose :display_name, as: :displayName
expose :description
expose :associations
expose :url
expose :label
expose :state
expose :updated_at, as: :lastUpdated, format_with: :iso8601
expose :pipeline_entity, as: :pipeline
expose :environment_entity, as: :environment
def issue_keys
return [] unless build&.pipeline.present?
@issue_keys ||= BuildEntity.new(build.pipeline).issue_keys
end
private
delegate :project, :deployable, :environment, :iid, :ref, :short_sha, to: :object
alias_method :deployment, :object
alias_method :build, :deployable
def associations
keys = issue_keys
[{ associationType: :issueKeys, values: keys }] if keys.present?
end
def display_name
"Deployment #{iid} (#{ref}@#{short_sha}) to #{environment.name}"
end
def label
"#{project.full_path}-#{environment.name}-#{iid}-#{short_sha}"
end
def description
"Deployment #{deployment.iid} of #{project.name} at #{short_sha} (#{build&.name}) to #{environment.name}"
end
def url
# There is no controller action to show a single deployment, so we
# link to the build instead
project_job_url(project, build) if build
end
def state
case deployment.status
when 'created' then 'pending'
when 'running' then 'in_progress'
when 'success' then 'successful'
when 'failed' then 'failed'
when 'canceled', 'skipped' then 'cancelled'
else
'unknown'
end
end
def schema_version
'1.0'
end
def pipeline_entity
PipelineEntity.new(build.pipeline) if build&.pipeline.present?
end
def environment_entity
EnvironmentEntity.new(environment)
end
def update_sequence_id
options[:update_sequence_id] || Client.generate_update_sequence_id
end
end
end
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
module Atlassian
module JiraConnect
module Serializers
class EnvironmentEntity < Grape::Entity
format_with(:string, &:to_s)
expose :id, format_with: :string
expose :display_name, as: :displayName
expose :type
private
alias_method :environment, :object
delegate :project, to: :object
def display_name
"#{project.name}/#{environment.name}"
end
def type
case environment.name
when /prod/i
'production'
when /test/i
'testing'
when /staging/i
'staging'
when /(dev|review)/i
'development'
else
'unmapped'
end
end
end
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Atlassian
module JiraConnect
module Serializers
# Both this an BuildEntity represent a Ci::Pipeline
class PipelineEntity < Grape::Entity
include Gitlab::Routing
format_with(:string, &:to_s)
expose :id, format_with: :string
expose :display_name, as: :displayName
expose :url
private
alias_method :pipeline, :object
delegate :project, to: :object
def display_name
"#{project.name} pipeline #{pipeline.iid}"
end
def url
project_pipeline_url(project, pipeline)
end
end
end
end
end

View File

@ -2502,9 +2502,6 @@ msgstr ""
msgid "AlertManagement|Open"
msgstr ""
msgid "AlertManagement|Opsgenie is enabled"
msgstr ""
msgid "AlertManagement|Please try again."
msgstr ""
@ -2568,15 +2565,9 @@ msgstr ""
msgid "AlertManagement|Value"
msgstr ""
msgid "AlertManagement|View alerts in Opsgenie"
msgstr ""
msgid "AlertManagement|View incident"
msgstr ""
msgid "AlertManagement|You have enabled the Opsgenie integration. Your alerts will be visible directly in Opsgenie."
msgstr ""
msgid "AlertMappingBuilder|Define fallback"
msgstr ""
@ -2604,9 +2595,6 @@ msgstr ""
msgid "AlertSettings|1. Select integration type"
msgstr ""
msgid "AlertSettings|2. Add link to your Opsgenie alert list"
msgstr ""
msgid "AlertSettings|2. Name integration"
msgstr ""
@ -2676,9 +2664,6 @@ msgstr ""
msgid "AlertSettings|Learn more about our our upcoming %{linkStart}integrations%{linkEnd}"
msgstr ""
msgid "AlertSettings|Opsgenie"
msgstr ""
msgid "AlertSettings|Proceed with editing"
msgstr ""
@ -2748,12 +2733,6 @@ msgstr ""
msgid "AlertSettings|Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
msgstr ""
msgid "AlertSettings|Utilizing this option will link the GitLab Alerts navigation item to your existing Opsgenie instance. By selecting this option, you cannot receive alerts from any other source in GitLab; it will effectively be turning Alerts within GitLab off as a feature."
msgstr ""
msgid "AlertSettings|We will soon be introducing the ability to create multiple unique HTTP endpoints. When this functionality is live, you will be able to configure an integration with Opsgenie to surface Opsgenie alerts in GitLab. This will replace the current Opsgenie integration which will be deprecated. %{linkStart}More Information%{linkEnd}"
msgstr ""
msgid "AlertSettings|Webhook URL"
msgstr ""
@ -7677,10 +7656,10 @@ msgstr ""
msgid "ContainerRegistry|Tags that match these rules are %{strongStart}removed%{strongEnd}, unless a rule above says to keep them."
msgstr ""
msgid "ContainerRegistry|Tags with names that match this regex pattern are kept. %{linkStart}More information%{linkEnd}"
msgid "ContainerRegistry|Tags with names that match this regex pattern are kept. %{linkStart}View regex examples.%{linkEnd}"
msgstr ""
msgid "ContainerRegistry|Tags with names that match this regex pattern are removed. %{linkStart}More information%{linkEnd}"
msgid "ContainerRegistry|Tags with names that match this regex pattern are removed. %{linkStart}View regex examples.%{linkEnd}"
msgstr ""
msgid "ContainerRegistry|The cleanup policy timed out before it could delete all tags. An administrator can %{adminLinkStart}manually run cleanup now%{adminLinkEnd} or you can wait for the cleanup policy to automatically run again. %{docLinkStart}More information%{docLinkEnd}"
@ -14346,6 +14325,9 @@ msgstr ""
msgid "Housekeeping, export, path, transfer, remove, archive."
msgstr ""
msgid "How does cleanup work?"
msgstr ""
msgid "How it works"
msgstr ""

View File

@ -44,7 +44,7 @@
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.178.0",
"@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "25.3.1",
"@gitlab/ui": "25.4.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4",

View File

@ -4,8 +4,7 @@ module QA
RSpec.describe 'Manage', :smoke do
describe 'Project creation' do
it 'user creates a new project',
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/429',
quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/283925', type: :investigating } do
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/429' do
Flow::Login.sign_in
created_project = Resource::Project.fabricate_via_browser_ui! do |project|

View File

@ -1,4 +1,4 @@
# frozen_string_literal: true
# Auto-require all cops under `rubocop/cop/**/*.rb`
Dir[File.join(__dir__, 'cop', '**', '*.rb')].each(&method(:require))
Dir[File.join(__dir__, 'cop', '**', '*.rb')].sort.each(&method(:require))

View File

@ -12,7 +12,7 @@ class GitalyTestBuild
include GitalyTest
def run
abort 'gitaly build failed' unless system(env, 'make', chdir: tmp_tests_gitaly_dir)
abort 'gitaly build failed' unless build_gitaly
ensure_gitlab_shell_secret!
check_gitaly_config!

View File

@ -8,6 +8,7 @@ class GitalyTestSpawn
include GitalyTest
def run
install_gitaly_gems if ENV['CI']
check_gitaly_config!
# # Uncomment line below to see all gitaly logs merged into CI trace

View File

@ -41,7 +41,7 @@ module GitalyTest
'HOME' => File.expand_path('tmp/tests'),
'GEM_PATH' => Gem.path.join(':'),
'BUNDLE_APP_CONFIG' => File.join(File.dirname(gemfile), '.bundle/config'),
'BUNDLE_FLAGS' => "--jobs=4 --retry=3 --quiet",
'BUNDLE_FLAGS' => "--jobs=4 --retry=3",
'BUNDLE_INSTALL_FLAGS' => nil,
'BUNDLE_GEMFILE' => gemfile,
'RUBYOPT' => nil,
@ -78,6 +78,14 @@ module GitalyTest
end
end
def install_gitaly_gems
system(env, "make #{tmp_tests_gitaly_dir}/.ruby-bundle", chdir: tmp_tests_gitaly_dir) # rubocop:disable GitlabSecurity/SystemCommandInjection
end
def build_gitaly
system(env, 'make', chdir: tmp_tests_gitaly_dir) # rubocop:disable GitlabSecurity/SystemCommandInjection
end
def start_gitaly
start(:gitaly)
end

View File

@ -225,24 +225,6 @@ RSpec.describe Ci::PipelinesForMergeRequestFinder do
branch_pipeline_2,
branch_pipeline])
end
context 'when ci_pipelines_for_merge_request_finder_new_cte feature flag is disabled' do
before do
stub_feature_flags(ci_pipelines_for_merge_request_finder_new_cte: false)
end
it 'returns only related merge request pipelines' do
expect(subject.all)
.to eq([detached_merge_request_pipeline,
branch_pipeline_2,
branch_pipeline])
expect(described_class.new(merge_request_2, nil).all)
.to eq([detached_merge_request_pipeline_2,
branch_pipeline_2,
branch_pipeline])
end
end
end
context 'when detached merge request pipeline is run on head ref of the merge request' do

View File

@ -31,17 +31,5 @@ describe('AlertManagementEmptyState', () => {
it('shows empty state', () => {
expect(EmptyState().exists()).toBe(true);
});
it('show OpsGenie integration state when OpsGenie mcv is true', () => {
mountComponent({
provide: {
alertManagementEnabled: false,
userCanEnableAlertManagement: false,
opsgenieMvcEnabled: true,
opsgenieMvcTargetUrl: 'https://opsgenie-url.com',
},
});
expect(EmptyState().props('title')).toBe('Opsgenie is enabled');
});
});
});

View File

@ -7,7 +7,5 @@
"populatingAlertsHelpUrl": "/link",
"emptyAlertSvgPath": "/link",
"alertManagementEnabled": false,
"userCanEnableAlertManagement": false,
"opsgenieMvcTargetUrl": "/link",
"opsgenieMvcEnabled": false
"userCanEnableAlertManagement": false
}

View File

@ -4,11 +4,10 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
"<form class=\\"gl-mt-6\\">
<h5 class=\\"gl-font-lg gl-my-5\\">Add new integrations</h5>
<div id=\\"integration-type\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"integration-type__BV_label_\\" for=\\"integration-type\\" class=\\"d-block col-form-label\\">1. Select integration type</label>
<div class=\\"bv-no-focus-ring\\"><select class=\\"gl-form-select custom-select\\" id=\\"__BVID__8\\">
<div class=\\"bv-no-focus-ring\\"><select class=\\"gl-form-select mw-100 custom-select\\" id=\\"__BVID__8\\">
<option value=\\"\\">Select integration type</option>
<option value=\\"HTTP\\">HTTP Endpoint</option>
<option value=\\"PROMETHEUS\\">External Prometheus</option>
<option value=\\"OPSGENIE\\">Opsgenie</option>
</select>
<!---->
<!---->

View File

@ -28,7 +28,6 @@ describe('AlertsSettingsFormNew', () => {
propsData: {
loading: false,
canAddIntegration: true,
canManageOpsgenie: true,
...props,
},
provide: {

View File

@ -4,7 +4,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { GlLoadingIcon } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
@ -376,17 +376,4 @@ describe('AlertsSettingsWrapper', () => {
});
});
});
// TODO: Will be removed in 13.7 as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/273657
describe('Opsgenie integration', () => {
it.each([true, false])('it shows/hides the alert when opsgenie is %s', (active) => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
provide: { opsgenie: { active } },
loading: false,
});
expect(wrapper.find(GlAlert).exists()).toBe(active);
});
});
});

View File

@ -19,12 +19,6 @@ export const defaultAlertSettingsConfig = {
url: PROMETHEUS_URL,
active: ACTIVE,
},
opsgenie: {
opsgenieMvcIsAvailable: true,
formPath: INVALID_URL,
active: ACTIVE,
opsgenieMvcTargetUrl: GENERIC_URL,
},
projectPath: '',
multiIntegrations: true,
};

View File

@ -1,6 +1,5 @@
import Vue from 'vue';
import { cloneDeep } from 'lodash';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { mount } from '@vue/test-utils';
import { getByText } from '@testing-library/dom';
import { createStore } from '~/mr_notes/stores';
import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
@ -59,7 +58,6 @@ describe('DiffExpansionCell', () => {
let mockFile;
let mockLine;
let store;
let vm;
beforeEach(() => {
mockFile = cloneDeep(diffFileMockData);
@ -70,7 +68,6 @@ describe('DiffExpansionCell', () => {
});
const createComponent = (options = {}) => {
const cmp = Vue.extend(DiffExpansionCell);
const defaults = {
fileHash: mockFile.file_hash,
contextLinesPath: 'contextLinesPath',
@ -78,46 +75,46 @@ describe('DiffExpansionCell', () => {
isTop: false,
isBottom: false,
};
const props = { ...defaults, ...options };
const propsData = { ...defaults, ...options };
vm = createComponentWithStore(cmp, store, props).$mount();
return mount(DiffExpansionCell, { store, propsData });
};
const findExpandUp = () => vm.$el.querySelector(EXPAND_UP_CLASS);
const findExpandDown = () => vm.$el.querySelector(EXPAND_DOWN_CLASS);
const findExpandAll = () => getByText(vm.$el, 'Show all unchanged lines');
const findExpandUp = (wrapper) => wrapper.find(EXPAND_UP_CLASS);
const findExpandDown = (wrapper) => wrapper.find(EXPAND_DOWN_CLASS);
const findExpandAll = ({ element }) => getByText(element, 'Show all unchanged lines');
describe('top row', () => {
it('should have "expand up" and "show all" option', () => {
createComponent({
const wrapper = createComponent({
isTop: true,
});
expect(findExpandUp()).not.toBe(null);
expect(findExpandDown()).toBe(null);
expect(findExpandAll()).not.toBe(null);
expect(findExpandUp(wrapper).exists()).toBe(true);
expect(findExpandDown(wrapper).exists()).toBe(false);
expect(findExpandAll(wrapper)).not.toBe(null);
});
});
describe('middle row', () => {
it('should have "expand down", "show all", "expand up" option', () => {
createComponent();
const wrapper = createComponent();
expect(findExpandUp()).not.toBe(null);
expect(findExpandDown()).not.toBe(null);
expect(findExpandAll()).not.toBe(null);
expect(findExpandUp(wrapper).exists()).toBe(true);
expect(findExpandDown(wrapper).exists()).toBe(true);
expect(findExpandAll(wrapper)).not.toBe(null);
});
});
describe('bottom row', () => {
it('should have "expand down" and "show all" option', () => {
createComponent({
const wrapper = createComponent({
isBottom: true,
});
expect(findExpandUp()).toBe(null);
expect(findExpandDown()).not.toBe(null);
expect(findExpandAll()).not.toBe(null);
expect(findExpandUp(wrapper).exists()).toBe(false);
expect(findExpandDown(wrapper).exists()).toBe(true);
expect(findExpandAll(wrapper)).not.toBe(null);
});
});
@ -144,9 +141,9 @@ describe('DiffExpansionCell', () => {
newLineNumber,
});
createComponent();
const wrapper = createComponent();
findExpandAll().click();
findExpandAll(wrapper).click();
expect(store.dispatch).toHaveBeenCalledWith(
'diffs/loadMoreLines',
@ -167,9 +164,9 @@ describe('DiffExpansionCell', () => {
const oldLineNumber = mockLine.meta_data.old_pos;
const newLineNumber = mockLine.meta_data.new_pos;
createComponent();
const wrapper = createComponent();
findExpandUp().click();
findExpandUp(wrapper).trigger('click');
expect(store.dispatch).toHaveBeenCalledWith(
'diffs/loadMoreLines',
@ -195,9 +192,9 @@ describe('DiffExpansionCell', () => {
mockLine.meta_data.old_pos = 200;
mockLine.meta_data.new_pos = 200;
createComponent();
const wrapper = createComponent();
findExpandDown().click();
findExpandDown(wrapper).trigger('click');
expect(store.dispatch).toHaveBeenCalledWith('diffs/loadMoreLines', {
endpoint: 'contextLinesPath',

View File

@ -1,33 +1,21 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { mount } from '@vue/test-utils';
import FileRowStats from '~/diffs/components/file_row_stats.vue';
describe('Diff file row stats', () => {
let Component;
let vm;
beforeAll(() => {
Component = Vue.extend(FileRowStats);
});
beforeEach(() => {
vm = mountComponent(Component, {
const wrapper = mount(FileRowStats, {
propsData: {
file: {
addedLines: 20,
removedLines: 10,
},
});
});
afterEach(() => {
vm.$destroy();
},
});
it('renders added lines count', () => {
expect(vm.$el.querySelector('.cgreen').textContent).toContain('+20');
expect(wrapper.find('.cgreen').text()).toContain('+20');
});
it('renders removed lines count', () => {
expect(vm.$el.querySelector('.cred').textContent).toContain('-10');
expect(wrapper.find('.cred').text()).toContain('-10');
});
});

View File

@ -32,7 +32,7 @@ exports[`Settings Form Keep N matches snapshot 1`] = `
exports[`Settings Form Keep Regex matches snapshot 1`] = `
<expiration-input-stub
data-testid="keep-regex-input"
description="Tags with names that match this regex pattern are kept. %{linkStart}More information%{linkEnd}"
description="Tags with names that match this regex pattern are kept. %{linkStart}View regex examples.%{linkEnd}"
error=""
label="Keep tags matching:"
name="keep-regex"
@ -54,7 +54,7 @@ exports[`Settings Form OlderThan matches snapshot 1`] = `
exports[`Settings Form Remove regex matches snapshot 1`] = `
<expiration-input-stub
data-testid="remove-regex-input"
description="Tags with names that match this regex pattern are removed. %{linkStart}More information%{linkEnd}"
description="Tags with names that match this regex pattern are removed. %{linkStart}View regex examples.%{linkEnd}"
error=""
label="Remove tags matching:"
name="remove-regex"

View File

@ -41,27 +41,6 @@ describe('ReviewerTitle component', () => {
});
});
describe('gutter toggle', () => {
it('does not show toggle by default', () => {
wrapper = createComponent({
numberOfReviewers: 2,
editable: false,
});
expect(wrapper.vm.$el.querySelector('.gutter-toggle')).toBeNull();
});
it('shows toggle when showToggle is true', () => {
wrapper = createComponent({
numberOfReviewers: 2,
editable: false,
showToggle: true,
});
expect(wrapper.vm.$el.querySelector('.gutter-toggle')).toEqual(expect.any(Object));
});
});
it('does not render spinner by default', () => {
wrapper = createComponent({
numberOfReviewers: 0,

View File

@ -1,135 +1,181 @@
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import eventHub from '~/vue_merge_request_widget/event_hub';
import component from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
import WidgetRebase from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
let wrapper;
function factory(propsData, mergeRequestWidgetGraphql) {
wrapper = shallowMount(WidgetRebase, {
propsData,
data() {
return {
state: {
rebaseInProgress: propsData.mr.rebaseInProgress,
targetBranch: propsData.mr.targetBranch,
userPermissions: {
pushToSourceBranch: propsData.mr.canPushToSourceBranch,
},
},
};
},
provide: { glFeatures: { mergeRequestWidgetGraphql } },
mocks: {
$apollo: {
queries: {
state: { loading: false },
},
},
},
});
}
describe('Merge request widget rebase component', () => {
let Component;
let vm;
const findRebaseMessageEl = () => vm.$el.querySelector('[data-testid="rebase-message"]');
const findRebaseMessageElText = () => findRebaseMessageEl().textContent.trim();
beforeEach(() => {
Component = Vue.extend(component);
});
const findRebaseMessageEl = () => wrapper.find('[data-testid="rebase-message"]');
const findRebaseMessageElText = () => findRebaseMessageEl().text();
afterEach(() => {
vm.$destroy();
wrapper.destroy();
wrapper = null;
});
describe('While rebasing', () => {
it('should show progress message', () => {
vm = mountComponent(Component, {
mr: { rebaseInProgress: true },
service: {},
[true, false].forEach((mergeRequestWidgetGraphql) => {
describe(`widget graphql is ${mergeRequestWidgetGraphql ? 'enabled' : 'dislabed'}`, () => {
describe('While rebasing', () => {
it('should show progress message', () => {
factory(
{
mr: { rebaseInProgress: true },
service: {},
},
mergeRequestWidgetGraphql,
);
expect(findRebaseMessageElText()).toContain('Rebase in progress');
});
});
expect(findRebaseMessageElText()).toContain('Rebase in progress');
});
});
describe('With permissions', () => {
beforeEach(() => {
vm = mountComponent(Component, {
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {},
});
});
it('it should render rebase button and warning message', () => {
const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.');
expect(text.replace(/\s\s+/g, ' ')).toContain(
'Rebase the source branch onto the target branch.',
);
});
it('it should render error message when it fails', (done) => {
vm.rebasingError = 'Something went wrong!';
Vue.nextTick(() => {
expect(findRebaseMessageElText()).toContain('Something went wrong!');
done();
});
});
});
describe('Without permissions', () => {
it('should render a message explaining user does not have permissions', () => {
vm = mountComponent(Component, {
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch: 'foo',
},
service: {},
});
const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.');
expect(text).toContain('Rebase the source branch onto');
expect(text).toContain('foo');
expect(text.replace(/\s\s+/g, ' ')).toContain('to allow this merge request to be merged.');
});
it('should render the correct target branch name', () => {
const targetBranch = 'fake-branch-to-test-with';
vm = mountComponent(Component, {
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch,
},
service: {},
});
const elem = findRebaseMessageEl();
expect(elem.innerHTML).toContain(
`Fast-forward merge is not possible. Rebase the source branch onto <span class="label-branch">${targetBranch}</span> to allow this merge request to be merged.`,
);
});
});
describe('methods', () => {
it('checkRebaseStatus', (done) => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
vm = mountComponent(Component, {
mr: {},
service: {
rebase() {
return Promise.resolve();
},
poll() {
return Promise.resolve({
data: {
rebase_in_progress: false,
merge_error: null,
describe('With permissions', () => {
it('it should render rebase button and warning message', () => {
factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
});
},
},
service: {},
},
mergeRequestWidgetGraphql,
);
const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.');
expect(text.replace(/\s\s+/g, ' ')).toContain(
'Rebase the source branch onto the target branch.',
);
});
it('it should render error message when it fails', async () => {
factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: true,
},
service: {},
},
mergeRequestWidgetGraphql,
);
wrapper.setData({ rebasingError: 'Something went wrong!' });
await nextTick();
expect(findRebaseMessageElText()).toContain('Something went wrong!');
});
});
vm.rebase();
describe('Without permissions', () => {
it('should render a message explaining user does not have permissions', () => {
factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch: 'foo',
},
service: {},
},
mergeRequestWidgetGraphql,
);
const text = findRebaseMessageElText();
expect(text).toContain('Fast-forward merge is not possible.');
expect(text).toContain('Rebase the source branch onto');
expect(text).toContain('foo');
expect(text.replace(/\s\s+/g, ' ')).toContain(
'to allow this merge request to be merged.',
);
});
it('should render the correct target branch name', () => {
const targetBranch = 'fake-branch-to-test-with';
factory(
{
mr: {
rebaseInProgress: false,
canPushToSourceBranch: false,
targetBranch,
},
service: {},
},
mergeRequestWidgetGraphql,
);
const elem = findRebaseMessageEl();
expect(elem.text()).toContain(
`Fast-forward merge is not possible. Rebase the source branch onto ${targetBranch} to allow this merge request to be merged.`,
);
});
});
describe('methods', () => {
it('checkRebaseStatus', async () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
factory(
{
mr: {},
service: {
rebase() {
return Promise.resolve();
},
poll() {
return Promise.resolve({
data: {
rebase_in_progress: false,
merge_error: null,
},
});
},
},
},
mergeRequestWidgetGraphql,
);
wrapper.vm.rebase();
// Wait for the rebase request
await nextTick();
// Wait for the polling request
await nextTick();
// Wait for the eventHub to be called
await nextTick();
// Wait for the rebase request
vm.$nextTick()
// Wait for the polling request
.then(vm.$nextTick())
// Wait for the eventHub to be called
.then(vm.$nextTick())
.then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess');
})
.then(done)
.catch(done.fail);
});
});
});
});
});

View File

@ -8,6 +8,15 @@ RSpec.describe Atlassian::JiraConnect::Client do
subject { described_class.new('https://gitlab-test.atlassian.net', 'sample_secret') }
let_it_be(:project) { create_default(:project, :repository) }
let_it_be(:mrs_by_title) { create_list(:merge_request, 4, :unique_branches, :jira_title) }
let_it_be(:mrs_by_branch) { create_list(:merge_request, 2, :jira_branch) }
let_it_be(:red_herrings) { create_list(:merge_request, 1, :unique_branches) }
let_it_be(:pipelines) do
(red_herrings + mrs_by_branch + mrs_by_title).map do |mr|
create(:ci_pipeline, merge_request: mr)
end
end
around do |example|
freeze_time { example.run }
@ -22,13 +31,19 @@ RSpec.describe Atlassian::JiraConnect::Client do
end
describe '#send_info' do
it 'calls store_build_info and store_dev_info as appropriate' do
it 'calls store_deploy_info, store_build_info and store_dev_info as appropriate' do
expect(subject).to receive(:store_build_info).with(
project: project,
update_sequence_id: :x,
pipelines: :y
).and_return(:build_stored)
expect(subject).to receive(:store_deploy_info).with(
project: project,
update_sequence_id: :x,
deployments: :q
).and_return(:deploys_stored)
expect(subject).to receive(:store_dev_info).with(
project: project,
update_sequence_id: :x,
@ -43,10 +58,12 @@ RSpec.describe Atlassian::JiraConnect::Client do
commits: :a,
branches: :b,
merge_requests: :c,
pipelines: :y
pipelines: :y,
deployments: :q
}
expect(subject.send_info(**args)).to contain_exactly(:dev_stored, :build_stored)
expect(subject.send_info(**args))
.to contain_exactly(:dev_stored, :build_stored, :deploys_stored)
end
it 'only calls methods that we need to call' do
@ -83,17 +100,65 @@ RSpec.describe Atlassian::JiraConnect::Client do
}
end
describe '#store_build_info' do
let_it_be(:mrs_by_title) { create_list(:merge_request, 4, :unique_branches, :jira_title) }
let_it_be(:mrs_by_branch) { create_list(:merge_request, 2, :jira_branch) }
let_it_be(:red_herrings) { create_list(:merge_request, 1, :unique_branches) }
let_it_be(:pipelines) do
(red_herrings + mrs_by_branch + mrs_by_title).map do |mr|
create(:ci_pipeline, merge_request: mr)
describe '#store_deploy_info' do
let_it_be(:environment) { create(:environment, name: 'DEV', project: project) }
let_it_be(:deployments) do
pipelines.map do |p|
build = create(:ci_build, environment: environment.name, pipeline: p, project: project)
create(:deployment, deployable: build, environment: environment)
end
end
let(:schema) do
Atlassian::Schemata.deploy_info_payload
end
let(:body) do
matcher = be_valid_json.according_to_schema(schema)
->(text) { matcher.matches?(text) }
end
before do
path = '/rest/deployments/0.1/bulk'
stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post)
.with(body: body, headers: expected_headers(path))
end
it "calls the API with auth headers" do
subject.send(:store_deploy_info, project: project, deployments: deployments)
end
it 'only sends information about relevant MRs' do
expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: have_attributes(size: 6) })
subject.send(:store_deploy_info, project: project, deployments: deployments)
end
it 'does not call the API if there is nothing to report' do
expect(subject).not_to receive(:post)
subject.send(:store_deploy_info, project: project, deployments: deployments.take(1))
end
it 'does not call the API if the feature flag is not enabled' do
stub_feature_flags(jira_sync_deployments: false)
expect(subject).not_to receive(:post)
subject.send(:store_deploy_info, project: project, deployments: deployments)
end
it 'does call the API if the feature flag enabled for the project' do
stub_feature_flags(jira_sync_deployments: project)
expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: Array })
subject.send(:store_deploy_info, project: project, deployments: deployments)
end
end
describe '#store_build_info' do
let(:build_info_payload_schema) do
Atlassian::Schemata.build_info_payload
end
@ -143,6 +208,8 @@ RSpec.describe Atlassian::JiraConnect::Client do
end
it 'avoids N+1 database queries' do
pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/292818'
baseline = ActiveRecord::QueryRecorder.new do
subject.send(:store_build_info, project: project, pipelines: pipelines)
end

View File

@ -0,0 +1,95 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Atlassian::JiraConnect::Serializers::DeploymentEntity do
let_it_be(:user) { create_default(:user) }
let_it_be(:project) { create_default(:project, :repository) }
let_it_be(:environment) { create(:environment, name: 'prod', project: project) }
let_it_be_with_reload(:deployment) { create(:deployment, environment: environment) }
subject { described_class.represent(deployment) }
context 'when the deployment does not belong to any Jira issue' do
describe '#issue_keys' do
it 'is empty' do
expect(subject.issue_keys).to be_empty
end
end
describe '#to_json' do
it 'can encode the object' do
expect(subject.to_json).to be_valid_json
end
it 'is invalid, since it has no issue keys' do
expect(subject.to_json).not_to be_valid_json.according_to_schema(Atlassian::Schemata.deployment_info)
end
end
end
context 'this is an external deployment' do
before do
deployment.update!(deployable: nil)
end
it 'does not raise errors when serializing' do
expect { subject.to_json }.not_to raise_error
end
it 'returns an empty list of issue keys' do
expect(subject.issue_keys).to be_empty
end
end
describe 'environment type' do
using RSpec::Parameterized::TableSyntax
where(:env_name, :env_type) do
'prod' | 'production'
'test' | 'testing'
'staging' | 'staging'
'dev' | 'development'
'review/app' | 'development'
'something-else' | 'unmapped'
end
with_them do
before do
environment.update!(name: env_name)
end
let(:exposed_type) { subject.send(:environment_entity).send(:type) }
it 'has the correct environment type' do
expect(exposed_type).to eq(env_type)
end
end
end
context 'when the deployment can be linked to a Jira issue' do
let(:pipeline) { create(:ci_pipeline, merge_request: merge_request) }
before do
subject.deployable.update!(pipeline: pipeline)
end
%i[jira_branch jira_title].each do |trait|
context "because it belongs to an MR with a #{trait}" do
let(:merge_request) { create(:merge_request, trait) }
describe '#issue_keys' do
it 'is not empty' do
expect(subject.issue_keys).not_to be_empty
end
end
describe '#to_json' do
it 'is valid according to the deployment info schema' do
expect(subject.to_json).to be_valid_json.according_to_schema(Atlassian::Schemata.deployment_info)
end
end
end
end
end
end

View File

@ -227,6 +227,56 @@ RSpec.describe Deployment do
deployment.skip!
end
end
describe 'synching status to Jira' do
let(:deployment) { create(:deployment) }
let(:worker) { ::JiraConnect::SyncDeploymentsWorker }
it 'calls the worker on creation' do
expect(worker).to receive(:perform_async).with(Integer)
deployment
end
it 'does not call the worker for skipped deployments' do
expect(deployment).to be_present # warm-up, ignore the creation trigger
expect(worker).not_to receive(:perform_async)
deployment.skip!
end
%i[run! succeed! drop! cancel!].each do |event|
context "when we call pipeline.#{event}" do
it 'triggers a Jira synch worker' do
expect(worker).to receive(:perform_async).with(deployment.id)
deployment.send(event)
end
context 'the feature is disabled' do
it 'does not trigger a worker' do
stub_feature_flags(jira_sync_deployments: false)
expect(worker).not_to receive(:perform_async)
deployment.send(event)
end
end
context 'the feature is enabled for this project' do
it 'does trigger a worker' do
stub_feature_flags(jira_sync_deployments: deployment.project)
expect(worker).to receive(:perform_async)
deployment.send(event)
end
end
end
end
end
end
describe '#success?' do

View File

@ -2,82 +2,185 @@
module Atlassian
module Schemata
def self.build_info
{
'type' => 'object',
'required' => %w(schemaVersion pipelineId buildNumber updateSequenceNumber displayName url state issueKeys testInfo references),
'properties' => {
'schemaVersion' => { 'type' => 'string', 'pattern' => '1.0' },
'pipelineId' => { 'type' => 'string' },
'buildNumber' => { 'type' => 'integer' },
'updateSequenceNumber' => { 'type' => 'integer' },
'displayName' => { 'type' => 'string' },
'url' => { 'type' => 'string' },
'state' => {
'type' => 'string',
'pattern' => '(pending|in_progress|successful|failed|cancelled)'
},
'issueKeys' => {
'type' => 'array',
'items' => { 'type' => 'string' },
'minItems' => 1
},
'testInfo' => {
'type' => 'object',
'required' => %w(totalNumber numberPassed numberFailed numberSkipped),
'properties' => {
'totalNumber' => { 'type' => 'integer' },
'numberFailed' => { 'type' => 'integer' },
'numberPassed' => { 'type' => 'integer' },
'numberSkipped' => { 'type' => 'integer' }
}
},
'references' => {
'type' => 'array',
'items' => {
class << self
def build_info
{
'type' => 'object',
'additionalProperties' => false,
'required' => %w(
schemaVersion pipelineId buildNumber updateSequenceNumber
displayName url state issueKeys testInfo references
lastUpdated
),
'properties' => {
'schemaVersion' => schema_version_type,
'pipelineId' => { 'type' => 'string' },
'buildNumber' => { 'type' => 'integer' },
'updateSequenceNumber' => { 'type' => 'integer' },
'displayName' => { 'type' => 'string' },
'lastUpdated' => { 'type' => 'string' },
'url' => { 'type' => 'string' },
'state' => state_type,
'issueKeys' => issue_keys_type,
'testInfo' => {
'type' => 'object',
'required' => %w(commit ref),
'required' => %w(totalNumber numberPassed numberFailed numberSkipped),
'properties' => {
'commit' => {
'type' => 'object',
'required' => %w(id repositoryUri),
'properties' => {
'id' => { 'type' => 'string' },
'repositoryUri' => { 'type' => 'string' }
}
},
'ref' => {
'type' => 'object',
'required' => %w(name uri),
'properties' => {
'name' => { 'type' => 'string' },
'uri' => { 'type' => 'string' }
'totalNumber' => { 'type' => 'integer' },
'numberFailed' => { 'type' => 'integer' },
'numberPassed' => { 'type' => 'integer' },
'numberSkipped' => { 'type' => 'integer' }
}
},
'references' => {
'type' => 'array',
'items' => {
'type' => 'object',
'required' => %w(commit ref),
'properties' => {
'commit' => {
'type' => 'object',
'required' => %w(id repositoryUri),
'properties' => {
'id' => { 'type' => 'string' },
'repositoryUri' => { 'type' => 'string' }
}
},
'ref' => {
'type' => 'object',
'required' => %w(name uri),
'properties' => {
'name' => { 'type' => 'string' },
'uri' => { 'type' => 'string' }
}
}
}
}
}
}
}
}
end
end
def self.build_info_payload
{
'type' => 'object',
'required' => %w(providerMetadata builds),
'properties' => {
'providerMetadata' => provider_metadata,
'builds' => { 'type' => 'array', 'items' => build_info }
def deployment_info
{
'type' => 'object',
'additionalProperties' => false,
'required' => %w(
deploymentSequenceNumber updateSequenceNumber
associations displayName url description lastUpdated
state pipeline environment
),
'properties' => {
'deploymentSequenceNumber' => { 'type' => 'integer' },
'updateSequenceNumber' => { 'type' => 'integer' },
'associations' => {
'type' => 'array',
'items' => association_type,
'minItems' => 1
},
'displayName' => { 'type' => 'string' },
'description' => { 'type' => 'string' },
'label' => { 'type' => 'string' },
'url' => { 'type' => 'string' },
'lastUpdated' => { 'type' => 'string' },
'state' => state_type,
'pipeline' => pipeline_type,
'environment' => environment_type,
'schemaVersion' => schema_version_type
}
}
}
end
end
def self.provider_metadata
{
'type' => 'object',
'required' => %w(product),
'properties' => { 'product' => { 'type' => 'string' } }
}
def environment_type
{
'type' => 'object',
'additionalProperties' => false,
'required' => %w(id displayName type),
'properties' => {
'id' => { 'type' => 'string', 'maxLength' => 255 },
'displayName' => { 'type' => 'string', 'maxLength' => 255 },
'type' => {
'type' => 'string',
'pattern' => '(unmapped|development|testing|staging|production)'
}
}
}
end
def pipeline_type
{
'type' => 'object',
'additionalProperties' => false,
'required' => %w(id displayName url),
'properties' => {
'id' => { 'type' => 'string', 'maxLength' => 255 },
'displayName' => { 'type' => 'string', 'maxLength' => 255 },
'url' => { 'type' => 'string', 'maxLength' => 2000 }
}
}
end
def schema_version_type
{ 'type' => 'string', 'pattern' => '1.0' }
end
def state_type
{
'type' => 'string',
'pattern' => '(pending|in_progress|successful|failed|cancelled)'
}
end
def association_type
{
'type' => 'object',
'additionalProperties' => false,
'required' => %w(associationType values),
'properties' => {
'associationType' => {
'type' => 'string',
'pattern' => '(issueKeys|issueIdOrKeys)'
},
'values' => issue_keys_type
}
}
end
def issue_keys_type
{
'type' => 'array',
'items' => { 'type' => 'string' },
'minItems' => 1,
'maxItems' => 100
}
end
def deploy_info_payload
payload('deployments', deployment_info)
end
def build_info_payload
payload('builds', build_info)
end
def payload(key, schema)
{
'type' => 'object',
'required' => ['providerMetadata', key],
'properties' => {
'providerMetadata' => provider_metadata,
key => { 'type' => 'array', 'items' => schema }
}
}
end
def provider_metadata
{
'type' => 'object',
'required' => %w(product),
'properties' => { 'product' => { 'type' => 'string' } }
}
end
end
end
end

View File

@ -33,7 +33,7 @@ Capybara.register_server :puma_via_workhorse do |app, port, host, **options|
socket_path = file.path
file.close! # We just want the filename
TestEnv.with_workhorse(TestEnv.workhorse_dir, host, port, socket_path) do
TestEnv.with_workhorse(host, port, socket_path) do
Capybara.servers[:puma].call(app, nil, socket_path, **options)
end
end

View File

@ -284,7 +284,7 @@ module TestEnv
@workhorse_path ||= File.join('tmp', 'tests', 'gitlab-workhorse')
end
def with_workhorse(workhorse_dir, host, port, upstream, &blk)
def with_workhorse(host, port, upstream, &blk)
host = "[#{host}]" if host.include?(':')
listen_addr = [host, port].join(':')

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::JiraConnect::SyncDeploymentsWorker do
include AfterNextHelpers
include ServicesHelper
describe '#perform' do
let_it_be(:deployment) { create(:deployment) }
let(:sequence_id) { Random.random_number(1..10_000) }
let(:object_id) { deployment.id }
subject { described_class.new.perform(object_id, sequence_id) }
context 'when the object exists' do
it 'calls the Jira sync service' do
expect_next(::JiraConnect::SyncService, deployment.project)
.to receive(:execute).with(deployments: contain_exactly(deployment), update_sequence_id: sequence_id)
subject
end
end
context 'when the object does not exist' do
let(:object_id) { non_existing_record_id }
it 'does not call the sync service' do
expect_next(::JiraConnect::SyncService).not_to receive(:execute)
subject
end
end
context 'when the feature flag is disabled' do
before do
stub_feature_flags(jira_sync_deployments: false)
end
it 'does not call the sync service' do
expect_next(::JiraConnect::SyncService).not_to receive(:execute)
subject
end
end
context 'when the feature flag is enabled for this project' do
before do
stub_feature_flags(jira_sync_deployments: deployment.project)
end
it 'calls the sync service' do
expect_next(::JiraConnect::SyncService).to receive(:execute)
subject
end
end
end
end

View File

@ -871,10 +871,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
"@gitlab/ui@25.3.1":
version "25.3.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.3.1.tgz#7557c810397f8c4b81c5360e4642afc3f8274dfc"
integrity sha512-vCl74UZgQ5m1caJk8O067KKYa+DP40ES2XDnM/wAc9mZAMynP0GPpePc3cmTLY8vpfzxx2A2iJr04SLgI2pxjA==
"@gitlab/ui@25.4.0":
version "25.4.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.4.0.tgz#67bfd2a905097fc4cec0272665c36c722b3475c9"
integrity sha512-/dffpdyDcX102wWTbzDQOmOGfAEyDitRWCBOk2U+WRKPJIsWYtZuw40putwNA/gUUE1U08TPHf3sGSLzwIKZPA==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"