Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-13 09:09:29 +00:00
parent 69eacec239
commit 778ea71394
61 changed files with 1334 additions and 381 deletions

View file

@ -40,6 +40,9 @@
.if-merge-request-title-update-caches: &if-merge-request-title-update-caches
if: '$CI_MERGE_REQUEST_TITLE =~ /UPDATE CACHE/'
.if-merge-request-title-run-all-rspec: &if-merge-request-title-run-all-rspec
if: '$CI_MERGE_REQUEST_TITLE =~ /RUN ALL RSPEC/'
.if-security-merge-request: &if-security-merge-request
if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID'
@ -440,22 +443,27 @@
.rails:rules:ee-and-foss-migration:
rules:
- changes: *db-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-unit:
rules:
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-integration:
rules:
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-system:
rules:
- changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-and-foss-fast_spec_helper:
rules:
- changes: ["config/**/*"]
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:default-refs-code-backstage-qa:
rules:
@ -467,24 +475,28 @@
- <<: *if-not-ee
when: never
- changes: *db-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-unit:
rules:
- <<: *if-not-ee
when: never
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-integration:
rules:
- <<: *if-not-ee
when: never
- changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:ee-only-system:
rules:
- <<: *if-not-ee
when: never
- changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:as-if-foss-migration:
rules:
@ -530,6 +542,7 @@
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-run-all-rspec
- <<: *if-merge-request
changes: *code-backstage-patterns
- <<: *if-master-refs
@ -556,6 +569,7 @@
- <<: *if-not-ee
when: never
- <<: *if-master-schedule-2-hourly
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:master-schedule-nightly--code-backstage:
rules:

View file

@ -1,11 +1,12 @@
<script>
import { GlDeprecatedButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { s__ } from '~/locale';
export default {
name: 'DeleteButton',
components: {
GlDeprecatedButton,
GlButton,
GlModal,
},
directives: {
@ -25,7 +26,12 @@ export default {
buttonVariant: {
type: String,
required: false,
default: '',
default: 'info',
},
buttonSize: {
type: String,
required: false,
default: 'medium',
},
hasSelectedDesigns: {
type: Boolean,
@ -38,27 +44,38 @@ export default {
modalId: uniqueId('design-deletion-confirmation-'),
};
},
modal: {
title: s__('DesignManagement|Delete designs confirmation'),
actionPrimary: {
text: s__('Delete'),
attributes: { variant: 'danger' },
},
actionCancel: {
text: s__('Cancel'),
},
},
};
</script>
<template>
<div>
<div class="gl-display-flex gl-align-items-center gl-h-full">
<gl-modal
:modal-id="modalId"
:title="s__('DesignManagement|Delete designs confirmation')"
:ok-title="s__('DesignManagement|Delete')"
ok-variant="danger"
:title="$options.modal.title"
:action-primary="$options.modal.actionPrimary"
:action-cancel="$options.modal.actionCancel"
@ok="$emit('deleteSelectedDesigns')"
>
<p>{{ s__('DesignManagement|Are you sure you want to delete the selected designs?') }}</p>
</gl-modal>
<gl-deprecated-button
<gl-button
v-gl-modal-directive="modalId"
:variant="buttonVariant"
:disabled="isDeleting || !hasSelectedDesigns"
:size="buttonSize"
:class="buttonClass"
:disabled="isDeleting || !hasSelectedDesigns"
>
<slot></slot>
</gl-deprecated-button>
</gl-button>
</div>
</template>

View file

@ -127,7 +127,7 @@ export default {
params: { id: filename },
query: $route.query,
}"
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
>
<div class="card-body p-0 d-flex-center overflow-hidden position-relative">
<div v-if="icon.name" class="design-event position-absolute">

View file

@ -1,10 +1,10 @@
<script>
import { GlDeprecatedButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import { VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
export default {
components: {
GlDeprecatedButton,
GlButton,
GlLoadingIcon,
},
directives: {
@ -30,7 +30,7 @@ export default {
<template>
<div>
<gl-deprecated-button
<gl-button
v-gl-tooltip.hover
:title="
s__(
@ -39,11 +39,12 @@ export default {
"
:disabled="isSaving"
variant="success"
size="small"
@click="openFileUpload"
>
{{ s__('DesignManagement|Upload designs') }}
<gl-loading-icon v-if="isSaving" inline class="ml-1" />
</gl-deprecated-button>
</gl-button>
<input
ref="fileUpload"

View file

@ -12,6 +12,12 @@ export default {
GlLink,
GlSprintf,
},
props: {
hasDesigns: {
type: Boolean,
required: true,
},
},
data() {
return {
dragCounter: 0,
@ -76,28 +82,29 @@ export default {
>
<slot>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
@click="openFileUpload"
>
<div class="d-flex-center flex-column text-center">
<gl-icon name="doc-new" :size="48" class="mb-4" />
<p>
<gl-sprintf
:message="
__(
'%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}.',
)
"
>
<template #lineOne="{ content }"
><span class="d-block">{{ content }}</span>
</template>
<template #link="{ content }">
<gl-link class="h-100 w-100" @click.stop="openFileUpload">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<div
:class="{ 'gl-flex-direction-column': hasDesigns }"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center"
data-testid="dropzone-area"
>
<gl-icon name="upload" :size="24" :class="hasDesigns ? 'gl-mb-2' : 'gl-mr-4'" />
<gl-sprintf
:message="
__(
'%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}',
)
"
>
<template #content="{ content }">
<span class="gl-font-weight-bold">{{ content }}&nbsp;</span>
</template>
<template #link="{ content }">
<gl-link @click.stop="openFileUpload">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div>
</button>
@ -117,7 +124,7 @@ export default {
class="card design-dropzone-border design-dropzone-overlay w-100 h-100 position-absolute d-flex-center p-3 bg-white"
>
<div v-show="!isDragDataValid" class="mw-50 text-center">
<h3>{{ __('Oh no!') }}</h3>
<h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Oh no!') }}</h3>
<span>{{
__(
'You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico.',
@ -125,7 +132,7 @@ export default {
}}</span>
</div>
<div v-show="isDragDataValid" class="mw-50 text-center">
<h3>{{ __('Incoming!') }}</h3>
<h3 :class="{ 'gl-font-base gl-display-inline': !hasDesigns }">{{ __('Incoming!') }}</h3>
<span>{{ __('Drop your designs to start your upload.') }}</span>
</div>
</div>

View file

@ -1,13 +1,13 @@
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import allVersionsMixin from '../../mixins/all_versions';
import { findVersionId } from '../../utils/design_management_utils';
export default {
components: {
GlDropdown,
GlDropdownItem,
GlNewDropdown,
GlNewDropdownItem,
},
mixins: [allVersionsMixin],
computed: {
@ -50,8 +50,8 @@ export default {
</script>
<template>
<gl-dropdown :text="dropdownText" variant="link" class="design-version-dropdown">
<gl-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
<gl-new-dropdown :text="dropdownText" size="small" class="design-version-dropdown">
<gl-new-dropdown-item v-for="(version, index) in allVersions" :key="version.node.id">
<router-link
class="d-flex js-version-link"
:to="{ path: $route.path, query: { version: findVersionId(version.node.id) } }"
@ -71,6 +71,6 @@ export default {
class="fa fa-check pull-right"
></i>
</router-link>
</gl-dropdown-item>
</gl-dropdown>
</gl-new-dropdown-item>
</gl-new-dropdown>
</template>

View file

@ -1,5 +1,5 @@
<script>
import { GlLoadingIcon, GlDeprecatedButton, GlAlert } from '@gitlab/ui';
import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import UploadButton from '../components/upload/button.vue';
@ -33,7 +33,7 @@ export default {
components: {
GlLoadingIcon,
GlAlert,
GlDeprecatedButton,
GlButton,
UploadButton,
Design,
DesignDestroyer,
@ -96,6 +96,14 @@ export default {
? s__('DesignManagement|Deselect all')
: s__('DesignManagement|Select all');
},
isDesignListEmpty() {
return !this.isSaving && !this.hasDesigns;
},
designDropzoneWrapperClass() {
return this.isDesignListEmpty
? 'col-12'
: 'gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3';
},
},
mounted() {
this.toggleOnPasteListener(this.$route.name);
@ -259,18 +267,22 @@ export default {
</script>
<template>
<div data-testid="designs-root">
<div data-testid="designs-root" class="gl-mt-2">
<header v-if="showToolbar" class="row-content-block border-top-0 p-2 d-flex">
<div class="d-flex justify-content-between align-items-center w-100">
<design-version-dropdown />
<div :class="['qa-selector-toolbar', { 'd-flex': hasDesigns, 'd-none': !hasDesigns }]">
<gl-deprecated-button
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full">
<div>
<span class="gl-font-weight-bold gl-mr-3">{{ s__('DesignManagement|Designs') }}</span>
<design-version-dropdown />
</div>
<div v-show="hasDesigns" class="qa-selector-toolbar gl-display-flex">
<gl-button
v-if="isLatestVersion"
variant="link"
class="mr-2 js-select-all"
size="small"
class="gl-mr-2 js-select-all"
@click="toggleDesignsSelection"
>{{ selectAllButtonText }}</gl-deprecated-button
>
>{{ selectAllButtonText }}
</gl-button>
<design-destroyer
#default="{ mutate, loading }"
:filenames="selectedDesigns"
@ -280,7 +292,9 @@ export default {
<delete-button
v-if="isLatestVersion"
:is-deleting="loading"
button-class="btn-danger btn-inverted mr-2"
button-variant="danger"
button-class="gl-mr-4"
button-size="small"
:has-selected-designs="hasSelectedDesigns"
@deleteSelectedDesigns="mutate()"
>
@ -298,11 +312,22 @@ export default {
{{ __('An error occurred while loading designs. Please try again.') }}
</gl-alert>
<ol v-else class="list-unstyled row">
<li class="col-md-6 col-lg-4 mb-3">
<design-dropzone class="design-list-item" @change="onUploadDesign" />
<span
v-if="isDesignListEmpty && !allVersions.length"
class="gl-font-weight-bold gl-font-weight-bold gl-ml-5 gl-mb-4"
>{{ s__('DesignManagement|Designs') }}</span
>
<li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper">
<design-dropzone
:class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }"
:has-designs="hasDesigns"
@change="onUploadDesign"
/>
</li>
<li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-4 mb-3">
<design-dropzone @change="onExistingDesignDropzoneChange($event, design.filename)"
<li v-for="design in designs" :key="design.id" class="col-md-6 col-lg-3 gl-mb-3">
<design-dropzone
:has-designs="hasDesigns"
@change="onExistingDesignDropzoneChange($event, design.filename)"
><design v-bind="design" :is-uploading="isDesignToBeSaved(design.filename)"
/></design-dropzone>

View file

@ -0,0 +1,156 @@
<script>
import {
GlButton,
GlSprintf,
GlLink,
GlIcon,
GlFormGroup,
GlFormCheckbox,
GlNewDropdown,
GlNewDropdownItem,
} from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import {
I18N_ALERT_SETTINGS_FORM,
NO_ISSUE_TEMPLATE_SELECTED,
TAKING_INCIDENT_ACTION_DOCS_LINK,
ISSUE_TEMPLATES_DOCS_LINK,
ERROR_MSG,
} from '../constants';
export default {
components: {
GlButton,
GlSprintf,
GlLink,
GlFormGroup,
GlIcon,
GlFormCheckbox,
GlNewDropdown,
GlNewDropdownItem,
},
inject: ['alertSettings', 'operationsSettingsEndpoint'],
data() {
return {
templates: [NO_ISSUE_TEMPLATE_SELECTED, ...this.alertSettings.templates],
createIssueEnabled: this.alertSettings.createIssue,
issueTemplate: this.alertSettings.issueTemplateKey,
sendEmailEnabled: this.alertSettings.sendEmail,
loading: false,
};
},
i18n: I18N_ALERT_SETTINGS_FORM,
TAKING_INCIDENT_ACTION_DOCS_LINK,
ISSUE_TEMPLATES_DOCS_LINK,
computed: {
issueTemplateHeader() {
return this.issueTemplate || NO_ISSUE_TEMPLATE_SELECTED.name;
},
formData() {
return {
create_issue: this.createIssueEnabled,
issue_template_key: this.issueTemplate,
send_email: this.sendEmailEnabled,
};
},
},
methods: {
selectIssueTemplate(templateKey) {
this.issueTemplate = templateKey;
},
isTemplateSelected(templateKey) {
return templateKey === this.issueTemplate;
},
updateAlertsIntegrationSettings() {
this.loading = true;
return axios
.patch(this.operationsSettingsEndpoint, {
project: {
incident_management_setting_attributes: this.formData,
},
})
.then(() => {
refreshCurrentPage();
})
.catch(({ response }) => {
const message = response?.data?.message || '';
createFlash(`${ERROR_MSG} ${message}`, 'alert');
})
.finally(() => {
this.loading = false;
});
},
},
};
</script>
<template>
<div>
<p>
<gl-sprintf :message="$options.i18n.introText">
<template #docsLink>
<gl-link :href="$options.TAKING_INCIDENT_ACTION_DOCS_LINK" target="_blank">
<span>{{ $options.i18n.introLinkText }}</span>
</gl-link>
</template>
</gl-sprintf>
</p>
<form ref="settingsForm" @submit.prevent="updateAlertsIntegrationSettings">
<gl-form-group class="gl-pl-0">
<gl-form-checkbox v-model="createIssueEnabled" data-qa-selector="create_issue_checkbox">
<span>{{ $options.i18n.createIssue.label }}</span>
</gl-form-checkbox>
</gl-form-group>
<gl-form-group
label-size="sm"
label-for="alert-integration-settings-issue-template"
class="col-8 col-md-9 gl-px-6"
>
<label class="gl-display-inline-flex" for="alert-integration-settings-issue-template">
{{ $options.i18n.issueTemplate.label }}
<gl-link :href="$options.ISSUE_TEMPLATES_DOCS_LINK" target="_blank">
<gl-icon name="question" :size="12" />
</gl-link>
</label>
<gl-new-dropdown
id="alert-integration-settings-issue-template"
data-qa-selector="incident_templates_dropdown"
:text="issueTemplateHeader"
:block="true"
>
<gl-new-dropdown-item
v-for="template in templates"
:key="template.key"
data-qa-selector="incident_templates_item"
:is-check-item="true"
:is-checked="isTemplateSelected(template.key)"
@click="selectIssueTemplate(template.key)"
>
{{ template.name }}
</gl-new-dropdown-item>
</gl-new-dropdown>
</gl-form-group>
<gl-form-group class="gl-pl-0 gl-mb-5">
<gl-form-checkbox v-model="sendEmailEnabled">
<span>{{ $options.i18n.sendEmail.label }}</span>
</gl-form-checkbox>
</gl-form-group>
<gl-button
ref="submitBtn"
data-qa-selector="save_changes_button"
:disabled="loading"
variant="success"
type="submit"
class="js-no-auto-disable"
>
{{ $options.i18n.saveBtnLabel }}
</gl-button>
</form>
</div>
</template>

View file

@ -0,0 +1,49 @@
<script>
import { GlButton, GlTabs, GlTab } from '@gitlab/ui';
import AlertsSettingsForm from './alerts_form.vue';
import { INTEGRATION_TABS_CONFIG, I18N_INTEGRATION_TABS } from '../constants';
export default {
components: {
GlButton,
GlTabs,
GlTab,
AlertsSettingsForm,
},
tabs: INTEGRATION_TABS_CONFIG,
i18n: I18N_INTEGRATION_TABS,
};
</script>
<template>
<section
id="incident-management-settings"
data-qa-selector="incidents_settings_content"
class="settings no-animate qa-incident-management-settings"
>
<div class="settings-header">
<h3 ref="sectionHeader" class="h4">
{{ $options.i18n.headerText }}
</h3>
<gl-button ref="toggleBtn" class="js-settings-toggle">{{
$options.i18n.expandBtnLabel
}}</gl-button>
<p ref="sectionSubHeader">
{{ $options.i18n.subHeaderText }}
</p>
</div>
<div class="settings-content">
<gl-tabs>
<gl-tab
v-for="(tab, index) in $options.tabs"
v-if="tab.active"
:key="`${tab.title}_${index}`"
:title="tab.title"
>
<component :is="tab.component" class="gl-pt-3" :data-testid="`${tab.component}-tab`" />
</gl-tab>
</gl-tabs>
</div>
</section>
</template>

View file

@ -0,0 +1,51 @@
import { __, s__ } from '~/locale';
export const INTEGRATION_TABS_CONFIG = [
{
title: s__('IncidentSettings|Alert integration'),
component: 'AlertsSettingsForm',
active: true,
},
{
title: s__('IncidentSettings|PagerDuty integration'),
component: '',
active: false,
},
{
title: s__('IncidentSettings|Grafana integration'),
component: '',
active: false,
},
];
export const I18N_INTEGRATION_TABS = {
headerText: s__('IncidentSettings|Incidents'),
expandBtnLabel: __('Expand'),
saveBtnLabel: __('Save changes'),
subHeaderText: s__(
'IncidentSettings|Set up integrations with external tools to help better manage incidents.',
),
};
export const I18N_ALERT_SETTINGS_FORM = {
saveBtnLabel: __('Save changes'),
introText: __('Action to take when receiving an alert. %{docsLink}'),
introLinkText: __('More information.'),
createIssue: {
label: __('Create an issue. Issues are created for each alert triggered.'),
},
issueTemplate: {
label: __('Issue template (optional)'),
},
sendEmail: {
label: __('Send a separate email notification to Developers.'),
},
};
export const NO_ISSUE_TEMPLATE_SELECTED = { key: '', name: __('No template selected') };
export const TAKING_INCIDENT_ACTION_DOCS_LINK =
'/help/user/project/integrations/prometheus#taking-action-on-incidents-ultimate';
export const ISSUE_TEMPLATES_DOCS_LINK =
'/help/user/project/description_templates#creating-issue-templates';
export const ERROR_MSG = __('There was an error saving your changes.');

View file

@ -0,0 +1,31 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import SettingsTabs from './components/incidents_settings_tabs.vue';
export default () => {
const el = document.querySelector('.js-incidents-settings');
if (!el) {
return null;
}
const {
dataset: { operationsSettingsEndpoint, templates, createIssue, issueTemplateKey, sendEmail },
} = el;
return new Vue({
el,
provide: {
operationsSettingsEndpoint,
alertSettings: {
templates: JSON.parse(templates),
createIssue: parseBoolean(createIssue),
issueTemplateKey,
sendEmail: parseBoolean(sendEmail),
},
},
render(createElement) {
return createElement(SettingsTabs);
},
});
};

View file

@ -3,8 +3,10 @@ import mountAlertsSettings from '~/alerts_settings';
import mountOperationSettings from '~/operation_settings';
import mountGrafanaIntegration from '~/grafana_integration';
import initSettingsPanels from '~/settings_panels';
import initIncidentsSettings from '~/incidents_settings';
document.addEventListener('DOMContentLoaded', () => {
initIncidentsSettings();
mountErrorTrackingForm();
mountOperationSettings();
mountGrafanaIntegration();

View file

@ -17,3 +17,8 @@
height: 230px;
}
}
// This is temporary class to be removed after feature flag removal: https://gitlab.com/gitlab-org/gitlab/-/issues/223197
.design-list-item-new {
height: 210px;
}

View file

@ -935,11 +935,10 @@ table.code {
}
}
.files:not([data-can-create-note='true']) .frame {
.files:not([data-can-create-note]) .frame {
cursor: auto;
}
.frame,
.frame.click-to-comment,
.btn-transparent.image-diff-overlay-add-comment {
position: relative;

View file

@ -11,7 +11,9 @@ module Projects
class << self
def valid_params
@valid_params ||= %i[page per_page search state]
@valid_params ||= %i[page per_page search state status author_username assignee_username]
# to permit array params you need to init them to an empty array
@valid_params << { labels: [] }
end
end

View file

@ -7,6 +7,9 @@ module Jira
extend ::Gitlab::Utils::Override
PER_PAGE = 100
DEFAULT_FIELDS = %w[assignee created creator id issuetype key
labels priority project reporter resolutiondate
status statuscategorychangeddate summary updated].join(',').freeze
def initialize(jira_service, params = {})
super(jira_service, params)
@ -22,7 +25,7 @@ module Jira
override :url
def url
"#{base_api_url}/search?jql=#{CGI.escape(jql)}&startAt=#{start_at}&maxResults=#{per_page}&fields=*all"
"#{base_api_url}/search?jql=#{CGI.escape(jql)}&startAt=#{start_at}&maxResults=#{per_page}&fields=#{DEFAULT_FIELDS}"
end
override :build_service_response

View file

@ -14,7 +14,7 @@ module Spam
end
def execute
external_spam_check_result = spam_verdict
external_spam_check_result = external_verdict
akismet_result = akismet_verdict
# filter out anything we don't recognise, including nils.
@ -38,7 +38,7 @@ module Spam
end
end
def spam_verdict
def external_verdict
return unless Gitlab::CurrentSettings.spam_check_endpoint_enabled
return if endpoint_url.blank?

View file

@ -4,15 +4,24 @@
- else
.js-design-management{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
- else
.mt-4
.row.empty-state
.col-12
.text-content
%h4.center
= _('The one place for your designs')
%p.center
- requirements_link_url = help_page_path('user/project/issues/design_management', anchor: 'requirements')
- requirements_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: requirements_link_url }
- support_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: support_url }
- link_end = '</a>'.html_safe
= s_("DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance.").html_safe % { requirements_link_start: requirements_link_start, requirements_link_end: link_end, support_link_start: support_link_start, support_link_end: link_end }
- if Feature.enabled?(:design_management_moved, @project)
.row.empty-state.design-dropzone-border.gl-mt-5
.text-content.center.gl-font-weight-bold
- requirements_link_url = help_page_path('user/project/issues/design_management', anchor: 'requirements')
- requirements_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: requirements_link_url }
- support_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: support_url }
- link_end = '</a>'.html_safe
= s_("DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance.").html_safe % { requirements_link_start: requirements_link_start, requirements_link_end: link_end, support_link_start: support_link_start, support_link_end: link_end }
- else
.mt-4
.row.empty-state
.col-12
.text-content
%h4.center
= _('The one place for your designs')
%p.center
- requirements_link_url = help_page_path('user/project/issues/design_management', anchor: 'requirements')
- requirements_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: requirements_link_url }
- support_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: support_url }
- link_end = '</a>'.html_safe
= s_("DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance.").html_safe % { requirements_link_start: requirements_link_start, requirements_link_end: link_end, support_link_start: support_link_start, support_link_end: link_end }

View file

@ -1,32 +1,8 @@
- templates = []
- setting = project_incident_management_setting
- templates = setting.available_issue_templates.map { |t| [t.name, t.key] }
- templates = setting.available_issue_templates.map { |t| { key: t.key, name: t.name } }
%section.settings.no-animate.qa-incident-management-settings{ data: { qa_selector: 'incidents_settings_content' } }
.settings-header
%h3{ :class => "h4" }= _('Incidents')
%button.btn.js-settings-toggle{ type: 'button' }
= _('Expand')
%p
= _('Action to take when receiving an alert.')
= link_to help_page_path('user/project/integrations/prometheus', anchor: 'taking-action-on-incidents-ultimate') do
= _('More information')
.settings-content
= form_for @project, url: project_settings_operations_path(@project), method: :patch do |f|
= form_errors(@project.incident_management_setting)
.form-group
= f.fields_for :incident_management_setting_attributes, setting do |form|
.form-group
= form.check_box :create_issue, data: { qa_selector: 'create_issue_checkbox' }
= form.label :create_issue, _('Create an issue. Issues are created for each alert triggered.'), class: 'form-check-label'
.form-group.col-sm-8
= form.label :issue_template_key, class: 'label-bold' do
= _('Issue template (optional)')
= link_to icon('question-circle'), help_page_path('user/project/description_templates', anchor: 'creating-issue-templates'), target: '_blank', rel: 'noopener noreferrer'
.select-wrapper
= form.select :issue_template_key, templates, {include_blank: 'No template selected'}, class: "form-control select-control", data: { qa_selector: 'incident_templates_dropdown' }
= icon('chevron-down')
.form-group
= form.check_box :send_email
= form.label :send_email, _('Send a separate email notification to Developers.'), class: 'form-check-label'
= f.submit _('Save changes'), class: 'btn btn-success', data: { qa_selector: 'save_changes_button' }
.js-incidents-settings{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
templates: templates.to_json,
create_issue: setting.create_issue.to_s,
issue_template_key: setting.issue_template_key.to_s,
send_email: setting.send_email.to_s } }

View file

@ -0,0 +1,5 @@
---
title: Move alert integrations setting to Vue
merge_request: 36110
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Fix to remove speech bubble on hover over image on MR Overview tab
merge_request: 36474
author: Adam Alvis @adamalvis
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix to display speech bubble on hover over image on commits page
merge_request: 36470
author: Adam Alvis @adamalvis
type: fixed

View file

@ -387,6 +387,11 @@ We follow the [PostgreSQL versions shipped with Omnibus GitLab](https://docs.git
Consult [GitLab tests in the Continuous Integration (CI) context](testing_guide/ci.md)
for more information.
We have dedicated jobs for each [testing level](testing_guide/testing_levels.md) and each job runs depending on the
changes made in your merge request.
If you want to force all the RSpec jobs to run regardless of your changes, you can include `RUN ALL RSPEC` in your merge
request title.
### Review app jobs
Consult the [Review Apps](testing_guide/review_apps.md) dedicated page for more information.

View file

@ -296,14 +296,16 @@ An approval is optional when a security report:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13067) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.3.
To enable License Approvals, a [project approval rule](../project/merge_requests/merge_request_approvals.md#multiple-approval-rules-premium)
must be created with the case-sensitive name `License-Check`. This approval group must be set
with the number of approvals required greater than zero.
`License-Check` is an approval rule you can enable to allow an individual or group to approve a
merge request that contains a `denied` license.
Once this group is added to your project, the approval rule is enabled for all Merge Requests. To
configure how this rule behaves, you can choose which licenses to `allow` or `deny` in the
[project policies for License Compliance](../compliance/license_compliance/index.md#policies)
section.
You can enable `License-Check` one of two ways:
- Create a [project approval rule](../project/merge_requests/merge_request_approvals.md#multiple-approval-rules-premium)
with the case-sensitive name `License-Check`.
- Create an approval group in the [project policies section for License Compliance](../compliance/license_compliance/index.md#policies).
You must set this approval group's number of approvals required to greater than zero. Once you
enable this group in your project, the approval rule is enabled for all merge requests.
Any code changes cause the approvals required to reset.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -46,7 +46,7 @@ When GitLab detects a **Denied** license, you can view it in the [license list](
You can view and modify existing policies from the [policies](#policies) tab.
![Edit Policy](img/policies_maintainer_edit_v13_0.png)
![Edit Policy](img/policies_maintainer_edit_v13_2.png)
## Use cases
@ -657,34 +657,39 @@ and the associated classifications for each.
Policies can be configured by maintainers of the project.
![Edit Policy](img/policies_maintainer_edit_v13_0.png)
![Add Policy](img/policies_maintainer_add_v13_0.png)
![Edit Policy](img/policies_maintainer_edit_v13_2.png)
![Add Policy](img/policies_maintainer_add_v13_2.png)
Developers of the project can view the policies configured in a project.
![View Policies](img/policies_v13_0.png)
## License Compliance report under pipelines
### Enabling License Approvals within a project
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5491) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13067) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.3.
From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on the
pipeline ID that has a `license_scanning` job to see the Licenses tab with the listed
licenses (if any).
`License-Check` is an approval rule you can enable to allow an approver, individual, or group to
approve a merge request that contains a `denied` license.
![License Compliance Pipeline Tab](img/license_compliance_pipeline_tab_v13_0.png)
You can enable `License-Check` one of two ways:
<!-- ## Troubleshooting
- Create a [project approval rule](../../project/merge_requests/merge_request_approvals.md#multiple-approval-rules-premium)
with the case-sensitive name `License-Check`.
- Create an approval group in the [project policies section for License Compliance](#policies).
You must set this approval group's number of approvals required to greater than zero. Once you
enable this group in your project, the approval rule is enabled for all merge requests.
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
one might have when setting this up, or when something is changed, or on upgrading, it's
important to describe those, too. Think of things that may go wrong and include them here.
This is important to minimize requests for support, and to avoid doc comments with
questions that you know someone might ask.
Any code changes cause the approvals required to reset.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
but commented out to help encourage others to add to it in the future. -->
An approval is required when a license report:
- Contains a dependency that includes a software license that is `denied`.
- Is not generated during pipeline execution.
An approval is optional when a license report:
- Contains no software license violations.
- Contains only new licenses that are `allowed` or unknown.
## Troubleshooting

View file

@ -43,7 +43,6 @@ module API
# Helper Methods for Grape Endpoint
module HelperMethods
prepend_if_ee('EE::API::APIGuard::HelperMethods') # rubocop: disable Cop/InjectEnterpriseEditionModule
include Gitlab::Auth::AuthFinders
def access_token
@ -66,7 +65,7 @@ module API
def find_user_from_sources
deploy_token_from_request ||
find_user_from_access_token ||
find_user_from_bearer_token ||
find_user_from_job_token ||
find_user_from_warden
end

View file

@ -41,6 +41,16 @@ module API
end
end
def job_token_authentication?
initial_current_user && @current_authenticated_job.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
# Returns the job associated with the token provided for
# authentication, if any
def current_authenticated_job
@current_authenticated_job
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# We can't rewrite this with StrongMemoize because `sudo!` would
# actually write to `@current_user`, and `sudo?` would immediately

View file

@ -54,6 +54,11 @@ module Gitlab
User.find_by_feed_token(token) || raise(UnauthorizedError)
end
def find_user_from_bearer_token
find_user_from_job_bearer_token ||
find_user_from_access_token
end
def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed]
return find_user_from_basic_auth_job if route_authentication_setting[:job_token_allowed] == :basic_auth
@ -136,6 +141,9 @@ module Gitlab
end
def validate_access_token!(scopes: [])
# return early if we've already authenticated via a job token
return if @current_authenticated_job.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
# return early if we've already authenticated via a deploy token
return if @current_authenticated_deploy_token.present? # rubocop:disable Gitlab/ModuleWithInstanceVariables
@ -155,6 +163,20 @@ module Gitlab
private
def find_user_from_job_bearer_token
return unless route_authentication_setting[:job_token_allowed]
token = parsed_oauth_token
return unless token
job = ::Ci::Build.find_by_token(token)
return unless job
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user
end
def route_authentication_setting
return {} unless respond_to?(:route_setting)

View file

@ -299,6 +299,9 @@ msgstr ""
msgid "%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd} and/or %{dependencyScanningLinkStart}Dependency Scanning%{dependencyScanningLinkEnd} must be enabled. %{securityBotLinkStart}GitLab-Security-Bot%{securityBotLinkEnd} will be the author of the auto-created merge request. %{moreInfoLinkStart}More information%{moreInfoLinkEnd}."
msgstr ""
msgid "%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
msgstr ""
msgid "%{cores} cores"
msgstr ""
@ -1289,7 +1292,7 @@ msgstr ""
msgid "Account: %{account}"
msgstr ""
msgid "Action to take when receiving an alert."
msgid "Action to take when receiving an alert. %{docsLink}"
msgstr ""
msgid "Actions"
@ -7958,6 +7961,9 @@ msgstr ""
msgid "DesignManagement|Deselect all"
msgstr ""
msgid "DesignManagement|Designs"
msgstr ""
msgid "DesignManagement|Discard comment"
msgstr ""
@ -8000,7 +8006,7 @@ msgstr ""
msgid "DesignManagement|The maximum number of designs allowed to be uploaded is %{upload_limit}. Please try again."
msgstr ""
msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
msgstr ""
msgid "DesignManagement|Unresolve thread"
@ -12407,7 +12413,19 @@ msgstr ""
msgid "Incident Management Limits"
msgstr ""
msgid "Incidents"
msgid "IncidentSettings|Alert integration"
msgstr ""
msgid "IncidentSettings|Grafana integration"
msgstr ""
msgid "IncidentSettings|Incidents"
msgstr ""
msgid "IncidentSettings|PagerDuty integration"
msgstr ""
msgid "IncidentSettings|Set up integrations with external tools to help better manage incidents."
msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
@ -15065,6 +15083,9 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
msgid "More information."
msgstr ""
msgid "More than %{number_commits_distance} commits different with %{default_branch}"
msgstr ""
@ -15634,6 +15655,9 @@ msgstr ""
msgid "No template"
msgstr ""
msgid "No template selected"
msgstr ""
msgid "No test coverage"
msgstr ""
@ -25418,9 +25442,24 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
msgid "UserLists|Add"
msgstr ""
msgid "UserLists|Add Users"
msgstr ""
msgid "UserLists|Add users"
msgstr ""
msgid "UserLists|Cancel"
msgstr ""
msgid "UserLists|Define a set of users to be used within feature flag strategies"
msgstr ""
msgid "UserLists|Enter a comma separated list of user IDs. These IDs should be the users of the system in which the feature flag is set, not GitLab IDs"
msgstr ""
msgid "UserLists|There are no users"
msgstr ""

View file

@ -43,7 +43,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.151.0",
"@gitlab/ui": "17.21.0",
"@gitlab/ui": "17.22.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2",

View file

@ -5,10 +5,11 @@ module QA
module Project
module Settings
class Incidents < Page::Base
view 'app/views/projects/settings/operations/_incidents.html.haml' do
view 'app/assets/javascripts/incidents_settings/components/alerts_form.vue' do
element :create_issue_checkbox
element :incident_templates_dropdown
element :save_changes_button
element :incident_templates_item
end
def enable_issues_for_incidents
@ -16,8 +17,9 @@ module QA
end
def select_issue_template(template)
click_element(:incident_templates_dropdown)
within_element :incident_templates_dropdown do
find(:option, template).select_option
find_element(:incident_templates_item, text: template).click
end
end

View file

@ -7,7 +7,7 @@ module QA
class Operations < Page::Base
include QA::Page::Settings::Common
view 'app/views/projects/settings/operations/_incidents.html.haml' do
view 'app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue' do
element :incidents_settings_content
end

View file

@ -45,15 +45,12 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
it 'updates form values' do
check(create_issue)
template_select = find_field('Issue template')
template_select.find(:xpath, 'option[2]').select_option
uncheck(send_email)
save_form
click_expand_incident_management_button
expect(find_field(create_issue)).to be_checked
expect(page).to have_select('Issue template', selected: 'bug')
expect(find_field(send_email)).not_to be_checked
end
@ -64,7 +61,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
end
def save_form
page.within "#edit_project_#{project.id}" do
page.within ".qa-incident-management-settings" do
click_on 'Save changes'
end
end

View file

@ -1,11 +1,11 @@
import { shallowMount } from '@vue/test-utils';
import { GlDeprecatedButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import BatchDeleteButton from '~/design_management_new/components/delete_button.vue';
describe('Batch delete button component', () => {
let wrapper;
const findButton = () => wrapper.find(GlDeprecatedButton);
const findButton = () => wrapper.find(GlButton);
const findModal = () => wrapper.find(GlModal);
function createComponent(isDeleting = false) {

View file

@ -10,7 +10,7 @@ exports[`Design management list item component when item appears in view after i
exports[`Design management list item component with no notes renders item with correct status icon for creation event 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
@ -78,7 +78,7 @@ exports[`Design management list item component with no notes renders item with c
exports[`Design management list item component with no notes renders item with correct status icon for deletion event 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
@ -146,7 +146,7 @@ exports[`Design management list item component with no notes renders item with c
exports[`Design management list item component with no notes renders item with correct status icon for modification event 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
@ -214,7 +214,7 @@ exports[`Design management list item component with no notes renders item with c
exports[`Design management list item component with no notes renders item with no status icon for none event 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
@ -269,7 +269,7 @@ exports[`Design management list item component with no notes renders item with n
exports[`Design management list item component with no notes renders loading spinner when isUploading is true 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
@ -329,7 +329,7 @@ exports[`Design management list item component with no notes renders loading spi
exports[`Design management list item component with notes renders item with multiple comments 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div
@ -401,7 +401,7 @@ exports[`Design management list item component with notes renders item with mult
exports[`Design management list item component with notes renders item with single comment 1`] = `
<router-link-stub
class="card cursor-pointer text-plain js-design-list-item design-list-item"
class="card cursor-pointer text-plain js-design-list-item design-list-item design-list-item-new"
to="[object Object]"
>
<div

View file

@ -50,6 +50,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
<delete-button-stub
buttonclass=""
buttonsize="medium"
buttonvariant="danger"
hasselecteddesigns="true"
>

View file

@ -4,8 +4,10 @@ exports[`Design management upload button component renders inverted upload desig
<div
isinverted="true"
>
<gl-deprecated-button-stub
size="md"
<gl-button-stub
category="tertiary"
icon=""
size="small"
title="Adding a design with the same filename replaces the file in a new version."
variant="success"
>
@ -13,7 +15,7 @@ exports[`Design management upload button component renders inverted upload desig
Upload designs
<!---->
</gl-deprecated-button-stub>
</gl-button-stub>
<input
accept="image/*"
@ -27,9 +29,11 @@ exports[`Design management upload button component renders inverted upload desig
exports[`Design management upload button component renders loading icon 1`] = `
<div>
<gl-deprecated-button-stub
<gl-button-stub
category="tertiary"
disabled="true"
size="md"
icon=""
size="small"
title="Adding a design with the same filename replaces the file in a new version."
variant="success"
>
@ -43,7 +47,7 @@ exports[`Design management upload button component renders loading icon 1`] = `
label="Loading"
size="sm"
/>
</gl-deprecated-button-stub>
</gl-button-stub>
<input
accept="image/*"
@ -57,8 +61,10 @@ exports[`Design management upload button component renders loading icon 1`] = `
exports[`Design management upload button component renders upload design button 1`] = `
<div>
<gl-deprecated-button-stub
size="md"
<gl-button-stub
category="tertiary"
icon=""
size="small"
title="Adding a design with the same filename replaces the file in a new version."
variant="success"
>
@ -66,7 +72,7 @@ exports[`Design management upload button component renders upload design button
Upload designs
<!---->
</gl-deprecated-button-stub>
</gl-button-stub>
<input
accept="image/*"

View file

@ -5,22 +5,21 @@ exports[`Design management dropzone component when dragging renders correct temp
class="w-100 position-relative"
>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
>
<div
class="d-flex-center flex-column text-center"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
data-testid="dropzone-area"
>
<gl-icon-stub
class="mb-4"
name="doc-new"
size="48"
class="gl-mb-2"
name="upload"
size="24"
/>
<p>
<gl-sprintf-stub
message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
/>
</p>
<gl-sprintf-stub
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
/>
</div>
</button>
@ -43,7 +42,9 @@ exports[`Design management dropzone component when dragging renders correct temp
class="mw-50 text-center"
style="display: none;"
>
<h3>
<h3
class=""
>
Oh no!
</h3>
@ -56,7 +57,9 @@ exports[`Design management dropzone component when dragging renders correct temp
class="mw-50 text-center"
style=""
>
<h3>
<h3
class=""
>
Incoming!
</h3>
@ -74,22 +77,21 @@ exports[`Design management dropzone component when dragging renders correct temp
class="w-100 position-relative"
>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
>
<div
class="d-flex-center flex-column text-center"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
data-testid="dropzone-area"
>
<gl-icon-stub
class="mb-4"
name="doc-new"
size="48"
class="gl-mb-2"
name="upload"
size="24"
/>
<p>
<gl-sprintf-stub
message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
/>
</p>
<gl-sprintf-stub
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
/>
</div>
</button>
@ -112,7 +114,9 @@ exports[`Design management dropzone component when dragging renders correct temp
class="mw-50 text-center"
style="display: none;"
>
<h3>
<h3
class=""
>
Oh no!
</h3>
@ -125,7 +129,9 @@ exports[`Design management dropzone component when dragging renders correct temp
class="mw-50 text-center"
style=""
>
<h3>
<h3
class=""
>
Incoming!
</h3>
@ -143,22 +149,21 @@ exports[`Design management dropzone component when dragging renders correct temp
class="w-100 position-relative"
>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
>
<div
class="d-flex-center flex-column text-center"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
data-testid="dropzone-area"
>
<gl-icon-stub
class="mb-4"
name="doc-new"
size="48"
class="gl-mb-2"
name="upload"
size="24"
/>
<p>
<gl-sprintf-stub
message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
/>
</p>
<gl-sprintf-stub
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
/>
</div>
</button>
@ -180,7 +185,9 @@ exports[`Design management dropzone component when dragging renders correct temp
<div
class="mw-50 text-center"
>
<h3>
<h3
class=""
>
Oh no!
</h3>
@ -193,7 +200,9 @@ exports[`Design management dropzone component when dragging renders correct temp
class="mw-50 text-center"
style="display: none;"
>
<h3>
<h3
class=""
>
Incoming!
</h3>
@ -211,22 +220,21 @@ exports[`Design management dropzone component when dragging renders correct temp
class="w-100 position-relative"
>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
>
<div
class="d-flex-center flex-column text-center"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
data-testid="dropzone-area"
>
<gl-icon-stub
class="mb-4"
name="doc-new"
size="48"
class="gl-mb-2"
name="upload"
size="24"
/>
<p>
<gl-sprintf-stub
message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
/>
</p>
<gl-sprintf-stub
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
/>
</div>
</button>
@ -248,7 +256,9 @@ exports[`Design management dropzone component when dragging renders correct temp
<div
class="mw-50 text-center"
>
<h3>
<h3
class=""
>
Oh no!
</h3>
@ -261,7 +271,9 @@ exports[`Design management dropzone component when dragging renders correct temp
class="mw-50 text-center"
style="display: none;"
>
<h3>
<h3
class=""
>
Incoming!
</h3>
@ -279,22 +291,21 @@ exports[`Design management dropzone component when dragging renders correct temp
class="w-100 position-relative"
>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
>
<div
class="d-flex-center flex-column text-center"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
data-testid="dropzone-area"
>
<gl-icon-stub
class="mb-4"
name="doc-new"
size="48"
class="gl-mb-2"
name="upload"
size="24"
/>
<p>
<gl-sprintf-stub
message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
/>
</p>
<gl-sprintf-stub
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
/>
</div>
</button>
@ -316,7 +327,9 @@ exports[`Design management dropzone component when dragging renders correct temp
<div
class="mw-50 text-center"
>
<h3>
<h3
class=""
>
Oh no!
</h3>
@ -329,7 +342,9 @@ exports[`Design management dropzone component when dragging renders correct temp
class="mw-50 text-center"
style="display: none;"
>
<h3>
<h3
class=""
>
Incoming!
</h3>
@ -347,22 +362,21 @@ exports[`Design management dropzone component when no slot provided renders defa
class="w-100 position-relative"
>
<button
class="card design-dropzone-card design-dropzone-border w-100 h-100 d-flex-center p-3"
class="card design-dropzone-card design-dropzone-border w-100 h-100 gl-align-items-center gl-justify-content-center gl-p-3"
>
<div
class="d-flex-center flex-column text-center"
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
data-testid="dropzone-area"
>
<gl-icon-stub
class="mb-4"
name="doc-new"
size="48"
class="gl-mb-2"
name="upload"
size="24"
/>
<p>
<gl-sprintf-stub
message="%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
/>
</p>
<gl-sprintf-stub
message="%{contentStart}Drop files to attach, or %{contentEnd}%{linkStart}browse%{linkEnd}"
/>
</div>
</button>
@ -384,7 +398,9 @@ exports[`Design management dropzone component when no slot provided renders defa
<div
class="mw-50 text-center"
>
<h3>
<h3
class=""
>
Oh no!
</h3>
@ -397,7 +413,9 @@ exports[`Design management dropzone component when no slot provided renders defa
class="mw-50 text-center"
style="display: none;"
>
<h3>
<h3
class=""
>
Incoming!
</h3>
@ -428,7 +446,9 @@ exports[`Design management dropzone component when slot provided renders dropzon
<div
class="mw-50 text-center"
>
<h3>
<h3
class=""
>
Oh no!
</h3>
@ -441,7 +461,9 @@ exports[`Design management dropzone component when slot provided renders dropzon
class="mw-50 text-center"
style="display: none;"
>
<h3>
<h3
class=""
>
Incoming!
</h3>

View file

@ -1,14 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
<gl-dropdown-stub
<gl-new-dropdown-stub
category="tertiary"
class="design-version-dropdown"
headertext=""
issueiid=""
projectpath=""
size="small"
text="Showing Latest Version"
variant="link"
variant="default"
>
<gl-dropdown-item-stub>
<gl-new-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
iconrightname=""
secondarytext=""
>
<router-link-stub
class="d-flex js-version-link"
to="[object Object]"
@ -31,8 +40,14 @@ exports[`Design management design version dropdown component renders design vers
class="fa fa-check pull-right"
/>
</router-link-stub>
</gl-dropdown-item-stub>
<gl-dropdown-item-stub>
</gl-new-dropdown-item-stub>
<gl-new-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
iconrightname=""
secondarytext=""
>
<router-link-stub
class="d-flex js-version-link"
to="[object Object]"
@ -51,19 +66,28 @@ exports[`Design management design version dropdown component renders design vers
<!---->
</router-link-stub>
</gl-dropdown-item-stub>
</gl-dropdown-stub>
</gl-new-dropdown-item-stub>
</gl-new-dropdown-stub>
`;
exports[`Design management design version dropdown component renders design version list 1`] = `
<gl-dropdown-stub
<gl-new-dropdown-stub
category="tertiary"
class="design-version-dropdown"
headertext=""
issueiid=""
projectpath=""
size="small"
text="Showing Latest Version"
variant="link"
variant="default"
>
<gl-dropdown-item-stub>
<gl-new-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
iconrightname=""
secondarytext=""
>
<router-link-stub
class="d-flex js-version-link"
to="[object Object]"
@ -86,8 +110,14 @@ exports[`Design management design version dropdown component renders design vers
class="fa fa-check pull-right"
/>
</router-link-stub>
</gl-dropdown-item-stub>
<gl-dropdown-item-stub>
</gl-new-dropdown-item-stub>
<gl-new-dropdown-item-stub
avatarurl=""
iconcolor=""
iconname=""
iconrightname=""
secondarytext=""
>
<router-link-stub
class="d-flex js-version-link"
to="[object Object]"
@ -106,6 +136,6 @@ exports[`Design management design version dropdown component renders design vers
<!---->
</router-link-stub>
</gl-dropdown-item-stub>
</gl-dropdown-stub>
</gl-new-dropdown-item-stub>
</gl-new-dropdown-stub>
`;

View file

@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import DesignDropzone from '~/design_management_new/components/upload/design_dropzone.vue';
import createFlash from '~/flash';
import { GlIcon } from '@gitlab/ui';
jest.mock('~/flash');
@ -12,10 +13,16 @@ describe('Design management dropzone component', () => {
};
const findDropzoneCard = () => wrapper.find('.design-dropzone-card');
const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]');
const findIcon = () => wrapper.find(GlIcon);
function createComponent({ slots = {}, data = {} } = {}) {
function createComponent({ slots = {}, data = {}, props = {} } = {}) {
wrapper = shallowMount(DesignDropzone, {
slots,
propsData: {
hasDesigns: true,
...props,
},
data() {
return data;
},
@ -129,4 +136,16 @@ describe('Design management dropzone component', () => {
});
});
});
it('applies correct classes when there are no designs or no design saving loader', () => {
createComponent({ props: { hasDesigns: false } });
expect(findDropzoneArea().classes()).not.toContain('gl-flex-direction-column');
expect(findIcon().classes()).toEqual(['gl-mr-4']);
});
it('applies correct classes when there are designs or design saving loader', () => {
createComponent({ props: { hasDesigns: true } });
expect(findDropzoneArea().classes()).toContain('gl-flex-direction-column');
expect(findIcon().classes()).toEqual(['gl-mb-2']);
});
});

View file

@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import DesignVersionDropdown from '~/design_management_new/components/upload/design_version_dropdown.vue';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlNewDropdown, GlNewDropdownItem } from '@gitlab/ui';
import mockAllVersions from './mock_data/all_versions';
const LATEST_VERSION_ID = 3;
@ -75,7 +75,7 @@ describe('Design management design version dropdown component', () => {
createComponent();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing Latest Version');
expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
});
});
@ -83,7 +83,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ maxVersions: 1 });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing Latest Version');
expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
});
});
@ -91,7 +91,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ $route: designRouteFactory(PREVIOUS_VERSION_ID) });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlDropdown).attributes('text')).toBe(`Showing Version #1`);
expect(wrapper.find(GlNewDropdown).attributes('text')).toBe(`Showing Version #1`);
});
});
@ -99,7 +99,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ $route: designRouteFactory(LATEST_VERSION_ID) });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing Latest Version');
expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing Latest Version');
});
});
@ -107,7 +107,7 @@ describe('Design management design version dropdown component', () => {
createComponent();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(GlDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
expect(wrapper.findAll(GlNewDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
});
});
});

View file

@ -2,6 +2,7 @@
exports[`Design management index page designs does not render toolbar when there is no permission 1`] = `
<div
class="gl-mt-2"
data-testid="designs-root"
>
<!---->
@ -12,18 +13,24 @@ exports[`Design management index page designs does not render toolbar when there
<ol
class="list-unstyled row"
>
<!---->
<li
class="col-md-6 col-lg-4 mb-3"
class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
data-testid="design-dropzone-wrapper"
>
<design-dropzone-stub
class="design-list-item"
class="design-list-item design-list-item-new"
hasdesigns="true"
/>
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-1-name"
@ -36,9 +43,11 @@ exports[`Design management index page designs does not render toolbar when there
<!---->
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-2-name"
@ -51,9 +60,11 @@ exports[`Design management index page designs does not render toolbar when there
<!---->
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-3-name"
@ -76,31 +87,44 @@ exports[`Design management index page designs does not render toolbar when there
exports[`Design management index page designs renders designs list and header with upload button 1`] = `
<div
class="gl-mt-2"
data-testid="designs-root"
>
<header
class="row-content-block border-top-0 p-2 d-flex"
>
<div
class="d-flex justify-content-between align-items-center w-100"
class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full"
>
<design-version-dropdown-stub />
<div>
<span
class="gl-font-weight-bold gl-mr-3"
>
Designs
</span>
<design-version-dropdown-stub />
</div>
<div
class="qa-selector-toolbar d-flex"
class="qa-selector-toolbar gl-display-flex"
>
<gl-deprecated-button-stub
class="mr-2 js-select-all"
size="md"
<gl-button-stub
category="tertiary"
class="gl-mr-2 js-select-all"
icon=""
size="small"
variant="link"
>
Select all
</gl-deprecated-button-stub>
</gl-button-stub>
<div>
<delete-button-stub
buttonclass="btn-danger btn-inverted mr-2"
buttonvariant=""
buttonclass="gl-mr-4"
buttonsize="small"
buttonvariant="danger"
>
Delete selected
@ -120,18 +144,24 @@ exports[`Design management index page designs renders designs list and header wi
<ol
class="list-unstyled row"
>
<!---->
<li
class="col-md-6 col-lg-4 mb-3"
class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
data-testid="design-dropzone-wrapper"
>
<design-dropzone-stub
class="design-list-item"
class="design-list-item design-list-item-new"
hasdesigns="true"
/>
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-1-name"
@ -147,9 +177,11 @@ exports[`Design management index page designs renders designs list and header wi
/>
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-2-name"
@ -165,9 +197,11 @@ exports[`Design management index page designs renders designs list and header wi
/>
</li>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-md-6 col-lg-3 gl-mb-3"
>
<design-dropzone-stub>
<design-dropzone-stub
hasdesigns="true"
>
<design-stub
event="NONE"
filename="design-3-name"
@ -193,6 +227,7 @@ exports[`Design management index page designs renders designs list and header wi
exports[`Design management index page designs renders error 1`] = `
<div
class="gl-mt-2"
data-testid="designs-root"
>
<!---->
@ -223,6 +258,7 @@ exports[`Design management index page designs renders error 1`] = `
exports[`Design management index page designs renders loading icon 1`] = `
<div
class="gl-mt-2"
data-testid="designs-root"
>
<!---->
@ -243,8 +279,9 @@ exports[`Design management index page designs renders loading icon 1`] = `
</div>
`;
exports[`Design management index page when has no designs renders empty text 1`] = `
exports[`Design management index page when has no designs renders design dropzone 1`] = `
<div
class="gl-mt-2"
data-testid="designs-root"
>
<!---->
@ -255,11 +292,18 @@ exports[`Design management index page when has no designs renders empty text 1`]
<ol
class="list-unstyled row"
>
<span
class="gl-font-weight-bold gl-font-weight-bold gl-ml-5 gl-mb-4"
>
Designs
</span>
<li
class="col-md-6 col-lg-4 mb-3"
class="col-12"
data-testid="design-dropzone-wrapper"
>
<design-dropzone-stub
class="design-list-item"
class=""
/>
</li>

View file

@ -68,6 +68,8 @@ describe('Design management index page', () => {
const findToolbar = () => wrapper.find('.qa-selector-toolbar');
const findDeleteButton = () => wrapper.find(DeleteButton);
const findDropzone = () => wrapper.findAll(DesignDropzone).at(0);
const dropzoneClasses = () => findDropzone().classes();
const findDropzoneWrapper = () => wrapper.find('[data-testid="design-dropzone-wrapper"]');
const findFirstDropzoneWithDesign = () => wrapper.findAll(DesignDropzone).at(1);
function createComponent({
@ -151,6 +153,22 @@ describe('Design management index page', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('has correct classes applied to design dropzone', () => {
createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
expect(dropzoneClasses()).toContain('design-list-item');
expect(dropzoneClasses()).toContain('design-list-item-new');
});
it('has correct classes applied to dropzone wrapper', () => {
createComponent({ designs: mockDesigns, allVersions: [mockVersion] });
expect(findDropzoneWrapper().classes()).toEqual([
'gl-flex-direction-column',
'col-md-6',
'col-lg-3',
'gl-mb-3',
]);
});
});
describe('when has no designs', () => {
@ -158,11 +176,20 @@ describe('Design management index page', () => {
createComponent();
});
it('renders empty text', () =>
it('renders design dropzone', () =>
wrapper.vm.$nextTick().then(() => {
expect(wrapper.element).toMatchSnapshot();
}));
it('has correct classes applied to design dropzone', () => {
expect(dropzoneClasses()).not.toContain('design-list-item');
expect(dropzoneClasses()).not.toContain('design-list-item-new');
});
it('has correct classes applied to dropzone wrapper', () => {
expect(findDropzoneWrapper().classes()).toEqual(['col-12']);
});
it('does not render a toolbar with buttons', () =>
wrapper.vm.$nextTick().then(() => {
expect(findToolbar().exists()).toBe(false);
@ -227,12 +254,18 @@ describe('Design management index page', () => {
},
};
return wrapper.vm.$nextTick().then(() => {
findDropzone().vm.$emit('change', [{ name: 'test' }]);
expect(mutate).toHaveBeenCalledWith(mutationVariables);
expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
expect(wrapper.vm.isSaving).toBeTruthy();
});
return wrapper.vm
.$nextTick()
.then(() => {
findDropzone().vm.$emit('change', [{ name: 'test' }]);
expect(mutate).toHaveBeenCalledWith(mutationVariables);
expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
expect(wrapper.vm.isSaving).toBeTruthy();
})
.then(() => {
expect(dropzoneClasses()).toContain('design-list-item');
expect(dropzoneClasses()).toContain('design-list-item-new');
});
});
it('sets isSaving', () => {
@ -380,8 +413,7 @@ describe('Design management index page', () => {
it('renders toolbar buttons', () => {
expect(findToolbar().exists()).toBe(true);
expect(findToolbar().classes()).toContain('d-flex');
expect(findToolbar().classes()).not.toContain('d-none');
expect(findToolbar().isVisible()).toBe(true);
});
it('adds two designs to selected designs when their checkboxes are checked', () => {
@ -440,7 +472,7 @@ describe('Design management index page', () => {
it('on latest version when has no designs toolbar buttons are invisible', () => {
createComponent({ designs: [], allVersions: [mockVersion] });
expect(findToolbar().classes()).toContain('d-none');
expect(findToolbar().isVisible()).toBe(false);
});
describe('on non-latest version', () => {

View file

@ -0,0 +1,99 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Alert integration settings form default state should match the default snapshot 1`] = `
<div>
<p>
<gl-sprintf-stub
message="Action to take when receiving an alert. %{docsLink}"
/>
</p>
<form>
<gl-form-group-stub
class="gl-pl-0"
>
<gl-form-checkbox-stub
checked="true"
data-qa-selector="create_issue_checkbox"
>
<span>
Create an issue. Issues are created for each alert triggered.
</span>
</gl-form-checkbox-stub>
</gl-form-group-stub>
<gl-form-group-stub
class="col-8 col-md-9 gl-px-6"
label-for="alert-integration-settings-issue-template"
label-size="sm"
>
<label
class="gl-display-inline-flex"
for="alert-integration-settings-issue-template"
>
Issue template (optional)
<gl-link-stub
href="/help/user/project/description_templates#creating-issue-templates"
target="_blank"
>
<gl-icon-stub
name="question"
size="12"
/>
</gl-link-stub>
</label>
<gl-new-dropdown-stub
block="true"
category="tertiary"
data-qa-selector="incident_templates_dropdown"
headertext=""
id="alert-integration-settings-issue-template"
size="medium"
text="selecte_tmpl"
variant="default"
>
<gl-new-dropdown-item-stub
avatarurl=""
data-qa-selector="incident_templates_item"
iconcolor=""
iconname=""
iconrightname=""
ischeckitem="true"
secondarytext=""
>
No template selected
</gl-new-dropdown-item-stub>
</gl-new-dropdown-stub>
</gl-form-group-stub>
<gl-form-group-stub
class="gl-pl-0 gl-mb-5"
>
<gl-form-checkbox-stub>
<span>
Send a separate email notification to Developers.
</span>
</gl-form-checkbox-stub>
</gl-form-group-stub>
<gl-button-stub
category="tertiary"
class="js-no-auto-disable"
data-qa-selector="save_changes_button"
icon=""
size="medium"
type="submit"
variant="success"
>
Save changes
</gl-button-stub>
</form>
</div>
`;

View file

@ -0,0 +1,56 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IncidentsSettingTabs should render the component 1`] = `
<section
class="settings no-animate qa-incident-management-settings"
data-qa-selector="incidents_settings_content"
id="incident-management-settings"
>
<div
class="settings-header"
>
<h3
class="h4"
>
Incidents
</h3>
<gl-button-stub
category="tertiary"
class="js-settings-toggle"
icon=""
size="medium"
variant="default"
>
Expand
</gl-button-stub>
<p>
Set up integrations with external tools to help better manage incidents.
</p>
</div>
<div
class="settings-content"
>
<gl-tabs-stub
theme="indigo"
>
<gl-tab-stub
title="Alert integration"
>
<alertssettingsform-stub
class="gl-pt-3"
data-testid="AlertsSettingsForm-tab"
/>
</gl-tab-stub>
<!---->
<!---->
</gl-tabs-stub>
</div>
</section>
`;

View file

@ -0,0 +1,71 @@
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import AlertsSettingsForm from '~/incidents_settings/components/alerts_form.vue';
import { ERROR_MSG } from '~/incidents_settings/constants';
import createFlash from '~/flash';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility');
describe('Alert integration settings form', () => {
let wrapper;
const findForm = () => wrapper.find({ ref: 'settingsForm' });
beforeEach(() => {
wrapper = shallowMount(AlertsSettingsForm, {
provide: {
operationsSettingsEndpoint: 'operations/endpoint',
alertSettings: {
issueTemplateKey: 'selecte_tmpl',
createIssue: true,
sendEmail: false,
templates: [],
},
},
});
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
describe('default state', () => {
it('should match the default snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('form', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('should refresh the page on successful submit', () => {
mock.onPatch().reply(200);
findForm().trigger('submit');
return waitForPromises().then(() => {
expect(refreshCurrentPage).toHaveBeenCalled();
});
});
it('should display a flah message on unsuccessful submit', () => {
mock.onPatch().reply(400);
findForm().trigger('submit');
return waitForPromises().then(() => {
expect(createFlash).toHaveBeenCalledWith(expect.stringContaining(ERROR_MSG), 'alert');
});
});
});
});

View file

@ -0,0 +1,53 @@
import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
import IncidentsSettingTabs from '~/incidents_settings/components/incidents_settings_tabs.vue';
describe('IncidentsSettingTabs', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(IncidentsSettingTabs);
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
const findToggleButton = () => wrapper.find({ ref: 'toggleBtn' });
const findSectionHeader = () => wrapper.find({ ref: 'sectionHeader' });
const findIntegrationTabs = () => wrapper.findAll(GlTab);
it('renders header text', () => {
expect(findSectionHeader().text()).toBe('Incidents');
});
describe('expand/collapse button', () => {
it('renders as an expand button by default', () => {
expect(findToggleButton().text()).toBe('Expand');
});
});
it('should render the component', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('should render the tab for each active integration', () => {
const activeTabs = wrapper.vm.$options.tabs.filter(tab => tab.active);
expect(findIntegrationTabs().length).toBe(activeTabs.length);
activeTabs.forEach((tab, index) => {
expect(
findIntegrationTabs()
.at(index)
.attributes('title'),
).toBe(tab.title);
expect(
findIntegrationTabs()
.at(index)
.find(`[data-testid="${tab.component}-tab"]`)
.exists(),
).toBe(true);
});
});
});

View file

@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
import { singleStatMetricsResult, singleStatMetricsWithFieldResult } from '../../mock_data';
import { singleStatGraphData } from '../../graph_data';
describe('Single Stat Chart component', () => {
let singleStatChart;
@ -8,7 +8,7 @@ describe('Single Stat Chart component', () => {
beforeEach(() => {
singleStatChart = shallowMount(SingleStatChart, {
propsData: {
graphData: singleStatMetricsResult,
graphData: singleStatGraphData({}, { unit: 'MB' }),
},
});
});
@ -20,15 +20,12 @@ describe('Single Stat Chart component', () => {
describe('computed', () => {
describe('statValue', () => {
it('should interpolate the value and unit props', () => {
expect(singleStatChart.vm.statValue).toBe('91.00MB');
expect(singleStatChart.vm.statValue).toBe('1.00MB');
});
it('should change the value representation to a percentile one', () => {
singleStatChart.setProps({
graphData: {
...singleStatMetricsResult,
maxValue: 120,
},
graphData: singleStatGraphData({ max_value: 120 }, { value: 91 }),
});
expect(singleStatChart.vm.statValue).toContain('75.83%');
@ -36,10 +33,7 @@ describe('Single Stat Chart component', () => {
it('should display NaN for non numeric maxValue values', () => {
singleStatChart.setProps({
graphData: {
...singleStatMetricsResult,
maxValue: 'not a number',
},
graphData: singleStatGraphData({ max_value: 'not a number' }),
});
expect(singleStatChart.vm.statValue).toContain('NaN');
@ -47,21 +41,7 @@ describe('Single Stat Chart component', () => {
it('should display NaN for missing query values', () => {
singleStatChart.setProps({
graphData: {
...singleStatMetricsResult,
metrics: [
{
...singleStatMetricsResult.metrics[0],
result: [
{
...singleStatMetricsResult.metrics[0].result[0],
value: [''],
},
],
},
],
maxValue: 120,
},
graphData: singleStatGraphData({ max_value: 120 }, { value: 'NaN' }),
});
expect(singleStatChart.vm.statValue).toContain('NaN');
@ -70,7 +50,7 @@ describe('Single Stat Chart component', () => {
describe('field attribute', () => {
it('displays a label value instead of metric value when field attribute is used', () => {
singleStatChart.setProps({
graphData: singleStatMetricsWithFieldResult,
graphData: singleStatGraphData({ field: 'job' }, { isVector: true }),
});
return singleStatChart.vm.$nextTick(() => {
@ -80,10 +60,7 @@ describe('Single Stat Chart component', () => {
it('displays No data to display if field attribute is not present', () => {
singleStatChart.setProps({
graphData: {
...singleStatMetricsWithFieldResult,
field: 'this-does-not-exist',
},
graphData: singleStatGraphData({ field: 'this-does-not-exist' }),
});
return singleStatChart.vm.$nextTick(() => {

View file

@ -15,11 +15,11 @@ import {
mockNamespace,
mockNamespacedData,
mockTimeRange,
singleStatMetricsResult,
graphDataPrometheusQueryRangeMultiTrack,
barMockData,
} from '../mock_data';
import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
import { singleStatGraphData } from '../graph_data';
import { panelTypes } from '~/monitoring/constants';
@ -232,7 +232,7 @@ describe('Dashboard Panel', () => {
data | component | hasCtxMenu
${dataWithType(panelTypes.AREA_CHART)} | ${MonitorTimeSeriesChart} | ${true}
${dataWithType(panelTypes.LINE_CHART)} | ${MonitorTimeSeriesChart} | ${true}
${singleStatMetricsResult} | ${MonitorSingleStatChart} | ${true}
${singleStatGraphData()} | ${MonitorSingleStatChart} | ${true}
${anomalyMockGraphData} | ${MonitorAnomalyChart} | ${false}
${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart} | ${false}
${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart} | ${false}

View file

@ -3,10 +3,40 @@ import { panelTypes, metricStates } from '~/monitoring/constants';
const initTime = 1435781451.781;
const makeValue = val => [initTime, val];
const makeValues = vals => vals.map((val, i) => [initTime + 15 * i, val]);
// Normalized Prometheus Responses
const scalarResult = ({ value = '1' } = {}) =>
normalizeQueryResponseData({
resultType: 'scalar',
result: makeValue(value),
});
const vectorResult = ({ value1 = '1', value2 = '2' } = {}) =>
normalizeQueryResponseData({
resultType: 'vector',
result: [
{
metric: {
__name__: 'up',
job: 'prometheus',
instance: 'localhost:9090',
},
value: makeValue(value1),
},
{
metric: {
__name__: 'up',
job: 'node',
instance: 'localhost:9100',
},
value: makeValue(value2),
},
],
});
const matrixSingleResult = ({ values = ['1', '2', '3'] } = {}) =>
normalizeQueryResponseData({
resultType: 'matrix',
@ -51,7 +81,6 @@ const matrixMultiResult = ({ values1 = ['1', '2', '3'], values2 = ['4', '5', '6'
* @param {Object} dataOptions.metricCount
* @param {Object} dataOptions.isMultiSeries
*/
// eslint-disable-next-line import/prefer-default-export
export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => {
const { metricCount = 1, isMultiSeries = false } = dataOptions;
@ -68,3 +97,30 @@ export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => {
...panelOptions,
});
};
/**
* Generate mock graph data according to options
*
* @param {Object} panelOptions - Panel options as in YML.
* @param {Object} dataOptions
* @param {Object} dataOptions.unit
* @param {Object} dataOptions.value
* @param {Object} dataOptions.isVector
*/
export const singleStatGraphData = (panelOptions = {}, dataOptions = {}) => {
const { unit, value = '1', isVector = false } = dataOptions;
return mapPanelToViewModel({
title: 'Single Stat Panel',
type: panelTypes.SINGLE_STAT,
metrics: [
{
label: 'Metric Label',
state: metricStates.OK,
result: isVector ? vectorResult({ value }) : scalarResult({ value }),
unit,
},
],
...panelOptions,
});
};

View file

@ -334,36 +334,6 @@ export const metricsResult = [
},
];
export const singleStatMetricsResult = {
title: 'Super Chart A2',
type: 'single-stat',
weight: 2,
metrics: [
{
id: 'metric_a1',
metricId: '2',
query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
unit: 'MB',
label: 'Total Consumption',
metric_id: 2,
prometheus_endpoint_path:
'/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
result: [
{
metric: { job: 'prometheus' },
value: ['2019-06-26T21:03:20.881Z', 91],
values: [['2019-06-26T21:03:20.881Z', 91]],
},
],
},
],
};
export const singleStatMetricsWithFieldResult = {
...singleStatMetricsResult,
field: 'job',
};
export const graphDataPrometheusQueryRangeMultiTrack = {
title: 'Super Chart A3',
type: 'heatmap',

View file

@ -1,13 +1,9 @@
import * as monitoringUtils from '~/monitoring/utils';
import * as urlUtils from '~/lib/utils/url_utility';
import { TEST_HOST } from 'jest/helpers/test_constants';
import {
mockProjectDir,
singleStatMetricsResult,
anomalyMockGraphData,
barMockData,
} from './mock_data';
import { mockProjectDir, anomalyMockGraphData, barMockData } from './mock_data';
import { metricsDashboardViewModel, graphData } from './fixture_data';
import { singleStatGraphData } from './graph_data';
const mockPath = `${TEST_HOST}${mockProjectDir}/-/environments/29/metrics`;
@ -82,7 +78,7 @@ describe('monitoring/utils', () => {
it('validates data with the query format', () => {
const validGraphData = monitoringUtils.graphDataValidatorForValues(
true,
singleStatMetricsResult,
singleStatGraphData(),
);
expect(validGraphData).toBe(true);
@ -105,7 +101,7 @@ describe('monitoring/utils', () => {
let threeMetrics;
let fourMetrics;
beforeEach(() => {
oneMetric = singleStatMetricsResult;
oneMetric = singleStatGraphData();
threeMetrics = anomalyMockGraphData;
const metrics = [...threeMetrics.metrics];

View file

@ -26,6 +26,63 @@ RSpec.describe Gitlab::Auth::AuthFinders do
env.merge!(basic_auth_header(username, password))
end
shared_examples 'find user from job token' do
context 'when route is allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: true } }
it "returns an Unauthorized exception for an invalid token" do
set_token('invalid token')
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
it "return user if token is valid" do
set_token(job.token)
expect(subject).to eq(user)
expect(@current_authenticated_job).to eq job
end
end
end
describe '#find_user_from_bearer_token' do
let(:job) { create(:ci_build, user: user) }
subject { find_user_from_bearer_token }
context 'when the token is passed as an oauth token' do
def set_token(token)
env['HTTP_AUTHORIZATION'] = "Bearer #{token}"
end
context 'with a job token' do
it_behaves_like 'find user from job token'
end
context 'with oauth token' do
let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
let(:token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api').token }
before do
set_token(token)
end
it { is_expected.to eq user }
end
end
context 'with a personal access token' do
let(:pat) { create(:personal_access_token, user: user) }
let(:token) { pat.token }
before do
env[described_class::PRIVATE_TOKEN_HEADER] = pat.token
end
it { is_expected.to eq user }
end
end
describe '#find_user_from_warden' do
context 'with CSRF token' do
before do
@ -522,8 +579,24 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end
describe '#validate_access_token!' do
subject { validate_access_token! }
let(:personal_access_token) { create(:personal_access_token, user: user) }
context 'with a job token' do
let(:route_authentication_setting) { { job_token_allowed: true } }
let(:job) { create(:ci_build, user: user) }
before do
env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
find_user_from_bearer_token
end
it 'does not raise an error' do
expect { subject }.not_to raise_error
end
end
it 'returns nil if no access_token present' do
expect(validate_access_token!).to be_nil
end

View file

@ -103,6 +103,12 @@ RSpec.describe Jira::Requests::Issues::ListService do
subject
end
end
it 'requests for default fields' do
expect(client).to receive(:get).with(include("fields=#{described_class::DEFAULT_FIELDS}")).and_return([])
subject
end
end
end
end

View file

@ -27,7 +27,7 @@ RSpec.describe Spam::SpamVerdictService do
before do
allow(service).to receive(:akismet_verdict).and_return(nil)
allow(service).to receive(:spam_verdict_verdict).and_return(nil)
allow(service).to receive(:external_verdict).and_return(nil)
end
context 'if all services return nil' do
@ -62,7 +62,7 @@ RSpec.describe Spam::SpamVerdictService do
context 'and they are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return(DISALLOW)
allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
allow(service).to receive(:external_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
@ -73,7 +73,7 @@ RSpec.describe Spam::SpamVerdictService do
context 'and one is supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
allow(service).to receive(:external_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
@ -84,7 +84,7 @@ RSpec.describe Spam::SpamVerdictService do
context 'and none are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
allow(service).to receive(:spam_verdict).and_return('rubbish')
allow(service).to receive(:external_verdict).and_return('rubbish')
end
it 'renders the more restrictive verdict' do
@ -149,8 +149,8 @@ RSpec.describe Spam::SpamVerdictService do
end
end
describe '#spam_verdict' do
subject { service.send(:spam_verdict) }
describe '#external_verdict' do
subject { service.send(:external_verdict) }
context 'if a Spam Check endpoint enabled and set to a URL' do
let(:spam_check_body) { {} }

View file

@ -47,7 +47,7 @@ module SimpleCovEnv
def configure_profile
SimpleCov.configure do
load_profile 'test_frameworks'
track_files '{app,config,danger,db,haml_lint,lib,qa,rubocop,scripts,tooling}/**/*.rb'
track_files '{app,config/initializers,config/initializers_before_autoloader,db/post_migrate,haml_lint,lib,rubocop,tooling}/**/*.rb'
add_filter '/vendor/ruby/'
add_filter '/app/controllers/sherlock/'
@ -73,8 +73,7 @@ module SimpleCovEnv
add_group 'Initializers', %w[config/initializers config/initializers_before_autoloader] # Matches EE files as well
add_group 'Migrations', %w[db/migrate db/optional_migrations db/post_migrate] # Matches EE files as well
add_group 'Libraries', %w[/lib /ee/lib]
add_group 'Tooling', %w[/danger /haml_lint /rubocop /scripts /tooling]
add_group 'QA', '/qa'
add_group 'Tooling', %w[/haml_lint /rubocop /tooling]
merge_timeout 365.days
end

View file

@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.151.0.tgz#099905295d33eb31033f4a48eb3652da2f686239"
integrity sha512-2PTSM8CFhUjeTFKfcq6E/YwPpOVdSVWupf3NhKO/bz/cisSBS5P7aWxaXKIaxy28ySyBKEfKaAT6b4rXTwvVgg==
"@gitlab/ui@17.21.0":
version "17.21.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.21.0.tgz#e881bac4540e3db29ee32e1dfd452677a445cd10"
integrity sha512-Ijh3QPlB3Y10Sk0f0eZ/rgRIKHGSzAWZLugw9sb+ppcn9OPbb+2vk0ZgCcdIrfkrX3G8tD8q0Ndl3K1nrz6a5g==
"@gitlab/ui@17.22.1":
version "17.22.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.22.1.tgz#368578d04bb49011690911599c22a7d306f5fe99"
integrity sha512-elcu2gdvt1Afz3GMrIBQR+eujlA6JetLn44T1UzPHUhlaodT/w+TIj0+uPIbPiD7Oz6uR/sYwBqlZXQdBcVv3Q==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"