Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
01fa7c10d9
commit
65b1882ddd
6
Gemfile
6
Gemfile
|
|
@ -133,6 +133,9 @@ gem 'apollo_upload_server', '~> 2.1.0'
|
|||
gem 'graphql-docs', '~> 2.1.0', group: [:development, :test]
|
||||
gem 'graphlient', '~> 0.5.0' # Used by BulkImport feature (group::import)
|
||||
|
||||
# Generate Fake data
|
||||
gem 'ffaker', '~> 2.10'
|
||||
|
||||
gem 'hashie', '~> 5.0.0'
|
||||
|
||||
# Pagination
|
||||
|
|
@ -415,9 +418,6 @@ group :development, :test do
|
|||
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
||||
gem 'minitest', '~> 5.11.0'
|
||||
|
||||
# Generate Fake data
|
||||
gem 'ffaker', '~> 2.10'
|
||||
|
||||
gem 'spring', '~> 4.1.0'
|
||||
gem 'spring-commands-rspec', '~> 1.0.4'
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
inModal: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -119,16 +124,28 @@ export default {
|
|||
exampleUrl,
|
||||
});
|
||||
},
|
||||
cancelButtonType() {
|
||||
return this.isEditing ? 'button' : 'reset';
|
||||
},
|
||||
saveText() {
|
||||
return this.isEditing ? s__('Badges|Save changes') : s__('Badges|Add badge');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// declared here to make it cancel-able
|
||||
this.debouncedPreview = debounce(function search() {
|
||||
this.renderBadge();
|
||||
}, badgePreviewDelayInMilliseconds);
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['addBadge', 'renderBadge', 'saveBadge', 'stopEditing', 'updateBadgeInForm']),
|
||||
debouncedPreview: debounce(function preview() {
|
||||
this.renderBadge();
|
||||
}, badgePreviewDelayInMilliseconds),
|
||||
onCancel() {
|
||||
this.stopEditing();
|
||||
updatePreview() {
|
||||
this.debouncedPreview();
|
||||
},
|
||||
onSubmit() {
|
||||
this.debouncedPreview.cancel();
|
||||
this.renderBadge();
|
||||
|
||||
const form = this.$el;
|
||||
if (!form.checkValidity()) {
|
||||
this.wasValidated = true;
|
||||
|
|
@ -161,6 +178,7 @@ export default {
|
|||
variant: VARIANT_INFO,
|
||||
});
|
||||
this.wasValidated = false;
|
||||
this.$emit('close-add-form');
|
||||
})
|
||||
.catch((error) => {
|
||||
createAlert({
|
||||
|
|
@ -171,6 +189,17 @@ export default {
|
|||
throw error;
|
||||
});
|
||||
},
|
||||
closeForm() {
|
||||
this.$refs.form.reset();
|
||||
this.$emit('close-add-form');
|
||||
},
|
||||
handleCancel() {
|
||||
if (this.isEditing) {
|
||||
this.stopEditing();
|
||||
} else {
|
||||
this.closeForm();
|
||||
}
|
||||
},
|
||||
},
|
||||
safeHtmlConfig: { ALLOW_TAGS: ['a', 'code'] },
|
||||
};
|
||||
|
|
@ -178,6 +207,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<form
|
||||
ref="form"
|
||||
:class="{ 'was-validated': wasValidated }"
|
||||
class="gl-mt-3 gl-mb-3 needs-validation"
|
||||
novalidate
|
||||
|
|
@ -197,7 +227,7 @@ export default {
|
|||
type="URL"
|
||||
class="form-control gl-form-input"
|
||||
required
|
||||
@input="debouncedPreview"
|
||||
@input="updatePreview"
|
||||
/>
|
||||
<div class="invalid-feedback">{{ s__('Badges|Enter a valid URL') }}</div>
|
||||
<span class="form-text text-muted">{{ badgeLinkUrlExample }}</span>
|
||||
|
|
@ -213,7 +243,7 @@ export default {
|
|||
type="URL"
|
||||
class="form-control gl-form-input"
|
||||
required
|
||||
@input="debouncedPreview"
|
||||
@input="updatePreview"
|
||||
/>
|
||||
<div class="invalid-feedback">{{ s__('Badges|Enter a valid URL') }}</div>
|
||||
<span class="form-text text-muted">{{ badgeImageUrlExample }}</span>
|
||||
|
|
@ -235,29 +265,23 @@ export default {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="isEditing" class="row-content-block">
|
||||
<gl-button class="btn-cancel gl-mr-4" data-testid="cancelEditing" @click="onCancel">
|
||||
{{ __('Cancel') }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
:loading="isSaving"
|
||||
type="submit"
|
||||
variant="confirm"
|
||||
category="primary"
|
||||
data-testid="saveEditing"
|
||||
>
|
||||
{{ s__('Badges|Save changes') }}
|
||||
</gl-button>
|
||||
</div>
|
||||
<div v-else class="form-group">
|
||||
<div v-if="!inModal" class="form-group" data-testid="action-buttons">
|
||||
<gl-button
|
||||
:loading="isSaving"
|
||||
type="submit"
|
||||
variant="confirm"
|
||||
category="primary"
|
||||
data-qa-selector="add_badge_button"
|
||||
class="gl-mr-3"
|
||||
>
|
||||
{{ s__('Badges|Add badge') }}
|
||||
{{ saveText }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
:type="cancelButtonType"
|
||||
data-qa-selector="cancel_badge_button"
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ __('Cancel') }}
|
||||
</gl-button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,42 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlBadge } from '@gitlab/ui';
|
||||
import { mapState } from 'vuex';
|
||||
import { GROUP_BADGE } from '../constants';
|
||||
import BadgeListRow from './badge_list_row.vue';
|
||||
import {
|
||||
GlBadge,
|
||||
GlLoadingIcon,
|
||||
GlTable,
|
||||
GlPagination,
|
||||
GlButton,
|
||||
GlModalDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { GROUP_BADGE, PROJECT_BADGE, INITIAL_PAGE, PAGE_SIZE } from '../constants';
|
||||
import Badge from './badge.vue';
|
||||
|
||||
export default {
|
||||
PAGE_SIZE,
|
||||
INITIAL_PAGE,
|
||||
name: 'BadgeList',
|
||||
components: {
|
||||
BadgeListRow,
|
||||
GlLoadingIcon,
|
||||
Badge,
|
||||
GlBadge,
|
||||
GlLoadingIcon,
|
||||
GlTable,
|
||||
GlPagination,
|
||||
GlButton,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
},
|
||||
i18n: {
|
||||
emptyGroupMessage: s__('Badges|This group has no badges, start by creating a new one above.'),
|
||||
emptyProjectMessage: s__(
|
||||
'Badges|This project has no badges, start by creating a new one above.',
|
||||
),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentPage: INITIAL_PAGE,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['badges', 'isLoading', 'kind']),
|
||||
|
|
@ -19,28 +46,123 @@ export default {
|
|||
isGroupBadge() {
|
||||
return this.kind === GROUP_BADGE;
|
||||
},
|
||||
showPagination() {
|
||||
return this.badges.length > PAGE_SIZE;
|
||||
},
|
||||
emptyMessage() {
|
||||
return this.isGroupBadge
|
||||
? this.$options.i18n.emptyGroupMessage
|
||||
: this.$options.i18n.emptyProjectMessage;
|
||||
},
|
||||
fields() {
|
||||
return [
|
||||
{
|
||||
key: 'name',
|
||||
label: __('Name'),
|
||||
tdClass: 'gl-vertical-align-middle!',
|
||||
},
|
||||
{
|
||||
key: 'badge',
|
||||
label: __('Badge'),
|
||||
tdClass: 'gl-vertical-align-middle!',
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
label: __('URL'),
|
||||
tdClass: 'gl-vertical-align-middle!',
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
label: __('Actions'),
|
||||
thClass: 'gl-text-right',
|
||||
tdClass: 'gl-text-right',
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['editBadge', 'updateBadgeInModal']),
|
||||
badgeKindText(item) {
|
||||
if (item.kind === PROJECT_BADGE) {
|
||||
return s__('Badges|Project Badge');
|
||||
}
|
||||
|
||||
return s__('Badges|Group Badge');
|
||||
},
|
||||
canEditBadge(item) {
|
||||
return item.kind === this.kind;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{ s__('Badges|Your badges') }}
|
||||
<gl-badge v-show="!isLoading" size="sm">{{ badges.length }}</gl-badge>
|
||||
</div>
|
||||
<gl-loading-icon v-show="isLoading" size="lg" class="card-body" />
|
||||
<div v-if="hasNoBadges" class="card-body">
|
||||
<span v-if="isGroupBadge">{{ s__('Badges|This group has no badges') }}</span>
|
||||
<span v-else>{{ s__('Badges|This project has no badges') }}</span>
|
||||
</div>
|
||||
<div v-else class="card-body" data-qa-selector="badge_list_content">
|
||||
<badge-list-row
|
||||
v-for="badge in badges"
|
||||
:key="badge.id"
|
||||
:badge="badge"
|
||||
data-qa-selector="badge_list_row"
|
||||
:data-qa-badge-name="badge.name"
|
||||
<div>
|
||||
<gl-loading-icon v-show="isLoading" size="md" />
|
||||
<div data-qa-selector="badge_list_content">
|
||||
<gl-table
|
||||
:empty-text="emptyMessage"
|
||||
:fields="fields"
|
||||
:items="badges"
|
||||
:per-page="$options.PAGE_SIZE"
|
||||
:current-page="currentPage"
|
||||
stacked="md"
|
||||
show-empty
|
||||
data-qa-selector="badge_list"
|
||||
>
|
||||
<template #cell(name)="{ item }">
|
||||
<label class="label-bold str-truncated mb-0">{{ item.name }}</label>
|
||||
<gl-badge size="sm">{{ badgeKindText(item) }}</gl-badge>
|
||||
</template>
|
||||
|
||||
<template #cell(badge)="{ item }">
|
||||
<badge :image-url="item.renderedImageUrl" :link-url="item.renderedLinkUrl" />
|
||||
</template>
|
||||
|
||||
<template #cell(url)="{ item }">
|
||||
{{ item.linkUrl }}
|
||||
</template>
|
||||
|
||||
<template #cell(actions)="{ item }">
|
||||
<div v-if="canEditBadge(item)" class="table-action-buttons" data-testid="badge-actions">
|
||||
<gl-button
|
||||
v-gl-modal.edit-badge-modal
|
||||
:disabled="item.isDeleting"
|
||||
class="gl-mr-3"
|
||||
variant="default"
|
||||
icon="pencil"
|
||||
size="medium"
|
||||
:aria-label="__('Edit')"
|
||||
data-testid="edit-badge-button"
|
||||
@click="editBadge(item)"
|
||||
/>
|
||||
<gl-button
|
||||
v-gl-modal.delete-badge-modal
|
||||
:disabled="item.isDeleting"
|
||||
category="secondary"
|
||||
variant="danger"
|
||||
icon="remove"
|
||||
size="medium"
|
||||
:aria-label="__('Delete')"
|
||||
data-testid="delete-badge"
|
||||
@click="updateBadgeInModal(item)"
|
||||
/>
|
||||
<gl-loading-icon v-show="item.isDeleting" size="sm" :inline="true" />
|
||||
</div>
|
||||
</template>
|
||||
</gl-table>
|
||||
|
||||
<gl-pagination
|
||||
v-if="showPagination"
|
||||
v-model="currentPage"
|
||||
:per-page="$options.PAGE_SIZE"
|
||||
:total-items="badges.length"
|
||||
:prev-text="__('Prev')"
|
||||
:next-text="__('Next')"
|
||||
:label-next-page="__('Go to next page')"
|
||||
:label-prev-page="__('Go to previous page')"
|
||||
align="center"
|
||||
class="gl-mt-5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlButton, GlModalDirective, GlBadge } from '@gitlab/ui';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { s__ } from '~/locale';
|
||||
import { PROJECT_BADGE } from '../constants';
|
||||
import Badge from './badge.vue';
|
||||
|
||||
export default {
|
||||
name: 'BadgeListRow',
|
||||
components: {
|
||||
Badge,
|
||||
GlLoadingIcon,
|
||||
GlButton,
|
||||
GlBadge,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
},
|
||||
props: {
|
||||
badge: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['kind']),
|
||||
badgeKindText() {
|
||||
if (this.badge.kind === PROJECT_BADGE) {
|
||||
return s__('Badges|Project Badge');
|
||||
}
|
||||
|
||||
return s__('Badges|Group Badge');
|
||||
},
|
||||
canEditBadge() {
|
||||
return this.badge.kind === this.kind;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['editBadge', 'updateBadgeInModal']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-responsive-table-row-layout gl-responsive-table-row">
|
||||
<badge
|
||||
:image-url="badge.renderedImageUrl"
|
||||
:link-url="badge.renderedLinkUrl"
|
||||
class="table-section section-30"
|
||||
/>
|
||||
<div class="table-section section-30">
|
||||
<label class="label-bold str-truncated mb-0">{{ badge.name }}</label>
|
||||
<gl-badge size="sm">{{ badgeKindText }}</gl-badge>
|
||||
</div>
|
||||
<span class="table-section section-30 str-truncated">{{ badge.linkUrl }}</span>
|
||||
<div class="table-section section-10 table-button-footer">
|
||||
<div v-if="canEditBadge" class="table-action-buttons">
|
||||
<gl-button
|
||||
:disabled="badge.isDeleting"
|
||||
class="gl-mr-3"
|
||||
variant="default"
|
||||
icon="pencil"
|
||||
size="medium"
|
||||
:aria-label="__('Edit')"
|
||||
@click="editBadge(badge)"
|
||||
/>
|
||||
<gl-button
|
||||
v-gl-modal.delete-badge-modal
|
||||
:disabled="badge.isDeleting"
|
||||
variant="danger"
|
||||
icon="remove"
|
||||
size="medium"
|
||||
:aria-label="__('Delete')"
|
||||
data-testid="delete-badge"
|
||||
@click="updateBadgeInModal(badge)"
|
||||
/>
|
||||
<gl-loading-icon v-show="badge.isDeleting" size="sm" :inline="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlSprintf, GlModal } from '@gitlab/ui';
|
||||
import { GlButton, GlCard, GlModal, GlIcon, GlSprintf } from '@gitlab/ui';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { createAlert, VARIANT_INFO } from '~/alert';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
|
@ -13,17 +13,34 @@ export default {
|
|||
Badge,
|
||||
BadgeForm,
|
||||
BadgeList,
|
||||
GlButton,
|
||||
GlCard,
|
||||
GlModal,
|
||||
GlIcon,
|
||||
GlSprintf,
|
||||
},
|
||||
i18n: {
|
||||
title: s__('Badges|Your badges'),
|
||||
addButton: s__('Badges|Add badge'),
|
||||
addFormTitle: s__('Badges|Add new badge'),
|
||||
deleteModalText: s__(
|
||||
'Badges|You are going to delete this badge. Deleted badges %{strongStart}cannot%{strongEnd} be restored.',
|
||||
),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addFormVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['badgeInModal', 'isEditing']),
|
||||
primaryProps() {
|
||||
...mapState(['badges', 'badgeInModal', 'isEditing']),
|
||||
saveProps() {
|
||||
return {
|
||||
text: __('Save changes'),
|
||||
attributes: { category: 'primary', variant: 'confirm' },
|
||||
};
|
||||
},
|
||||
deleteProps() {
|
||||
return {
|
||||
text: __('Delete badge'),
|
||||
attributes: { category: 'primary', variant: 'danger' },
|
||||
|
|
@ -37,7 +54,16 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
...mapActions(['deleteBadge']),
|
||||
onSubmitModal() {
|
||||
showAddForm() {
|
||||
this.addFormVisible = !this.addFormVisible;
|
||||
},
|
||||
closeAddForm() {
|
||||
this.addFormVisible = false;
|
||||
},
|
||||
onSubmitEditModal() {
|
||||
this.$refs.editForm.onSubmit();
|
||||
},
|
||||
onSubmitDeleteModal() {
|
||||
this.deleteBadge(this.badgeInModal)
|
||||
.then(() => {
|
||||
createAlert({
|
||||
|
|
@ -58,12 +84,58 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="badge-settings">
|
||||
<gl-card
|
||||
class="gl-new-card"
|
||||
header-class="gl-new-card-header"
|
||||
body-class="gl-new-card-body gl-overflow-hidden gl-px-0"
|
||||
>
|
||||
<template #header>
|
||||
<div class="gl-new-card-title-wrapper">
|
||||
<h3 class="gl-new-card-title">{{ $options.i18n.title }}</h3>
|
||||
<span class="gl-new-card-count">
|
||||
<gl-icon name="labels" class="gl-mr-2" />
|
||||
{{ badges.length }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="gl-new-card-actions">
|
||||
<gl-button
|
||||
v-if="!addFormVisible"
|
||||
size="small"
|
||||
data-testid="show-badge-add-form"
|
||||
@click="showAddForm"
|
||||
>{{ $options.i18n.addButton }}</gl-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="addFormVisible" class="gl-new-card-add-form gl-m-5">
|
||||
<h4 class="gl-mt-0">{{ $options.i18n.addFormTitle }}</h4>
|
||||
<badge-form
|
||||
:is-editing="false"
|
||||
data-testid="add-new-badge"
|
||||
@close-add-form="closeAddForm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<badge-list />
|
||||
</gl-card>
|
||||
|
||||
<gl-modal
|
||||
modal-id="edit-badge-modal"
|
||||
:title="s__('Badges|Edit badge')"
|
||||
:action-primary="saveProps"
|
||||
:action-cancel="cancelProps"
|
||||
@primary="onSubmitEditModal"
|
||||
>
|
||||
<badge-form ref="editForm" :is-editing="true" :in-modal="true" data-testid="edit-badge" />
|
||||
</gl-modal>
|
||||
|
||||
<gl-modal
|
||||
modal-id="delete-badge-modal"
|
||||
:title="s__('Badges|Delete badge?')"
|
||||
:action-primary="primaryProps"
|
||||
:action-primary="deleteProps"
|
||||
:action-cancel="cancelProps"
|
||||
@primary="onSubmitModal"
|
||||
@primary="onSubmitDeleteModal"
|
||||
>
|
||||
<div class="well">
|
||||
<badge
|
||||
|
|
@ -79,10 +151,5 @@ export default {
|
|||
</gl-sprintf>
|
||||
</p>
|
||||
</gl-modal>
|
||||
|
||||
<badge-form v-show="isEditing" :is-editing="true" data-testid="edit-badge" />
|
||||
|
||||
<badge-form v-show="!isEditing" :is-editing="false" data-testid="add-new-badge" />
|
||||
<badge-list v-show="!isEditing" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -8,3 +8,5 @@ export const PLACEHOLDERS = [
|
|||
'default_branch',
|
||||
'commit_sha',
|
||||
];
|
||||
export const INITIAL_PAGE = 1;
|
||||
export const PAGE_SIZE = 10;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
<script>
|
||||
import {
|
||||
EVENT_CLOSED_I18N,
|
||||
TARGET_TYPE_MERGE_REQUEST,
|
||||
EVENT_CLOSED_ICONS,
|
||||
} from 'ee_else_ce/contribution_events/constants';
|
||||
import ContributionEventBase from './contribution_event_base.vue';
|
||||
|
||||
export default {
|
||||
name: 'ContributionEventClosed',
|
||||
components: { ContributionEventBase },
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
target() {
|
||||
return this.event.target;
|
||||
},
|
||||
targetType() {
|
||||
return this.target.type;
|
||||
},
|
||||
issueType() {
|
||||
return this.target.issue_type;
|
||||
},
|
||||
message() {
|
||||
return EVENT_CLOSED_I18N[this.issueType || this.targetType] || EVENT_CLOSED_I18N.fallback;
|
||||
},
|
||||
iconName() {
|
||||
return EVENT_CLOSED_ICONS[this.issueType || this.targetType] || EVENT_CLOSED_ICONS.fallback;
|
||||
},
|
||||
iconClass() {
|
||||
return this.targetType === TARGET_TYPE_MERGE_REQUEST ? 'gl-text-red-500' : 'gl-text-blue-500';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<contribution-event-base
|
||||
:event="event"
|
||||
:message="message"
|
||||
:icon-name="iconName"
|
||||
:icon-class="iconClass"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -1,14 +1,9 @@
|
|||
<script>
|
||||
import {
|
||||
EVENT_CREATED_I18N,
|
||||
TARGET_TYPE_WORK_ITEM,
|
||||
TARGET_TYPE_DESIGN,
|
||||
} from 'ee_else_ce/contribution_events/constants';
|
||||
import { EVENT_CREATED_I18N, TARGET_TYPE_DESIGN } from 'ee_else_ce/contribution_events/constants';
|
||||
import ContributionEventBase from './contribution_event_base.vue';
|
||||
|
||||
export default {
|
||||
name: 'ContributionEventCreated',
|
||||
i18n: EVENT_CREATED_I18N,
|
||||
components: { ContributionEventBase },
|
||||
props: {
|
||||
event: {
|
||||
|
|
@ -23,16 +18,15 @@ export default {
|
|||
resourceParent() {
|
||||
return this.event.resource_parent;
|
||||
},
|
||||
issueType() {
|
||||
return this.target.issue_type;
|
||||
},
|
||||
message() {
|
||||
if (!this.target) {
|
||||
return this.$options.i18n[this.resourceParent.type] || this.$options.i18n.fallback;
|
||||
return EVENT_CREATED_I18N[this.resourceParent.type] || EVENT_CREATED_I18N.fallback;
|
||||
}
|
||||
|
||||
if (this.target.type === TARGET_TYPE_WORK_ITEM) {
|
||||
return this.$options.i18n[this.target.issue_type] || this.$options.i18n.fallback;
|
||||
}
|
||||
|
||||
return this.$options.i18n[this.target.type] || this.$options.i18n.fallback;
|
||||
return EVENT_CREATED_I18N[this.issueType || this.target.type] || EVENT_CREATED_I18N.fallback;
|
||||
},
|
||||
iconName() {
|
||||
switch (this.target?.type) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
EVENT_TYPE_PRIVATE,
|
||||
EVENT_TYPE_MERGED,
|
||||
EVENT_TYPE_CREATED,
|
||||
EVENT_TYPE_CLOSED,
|
||||
} from '../constants';
|
||||
import ContributionEventApproved from './contribution_event/contribution_event_approved.vue';
|
||||
import ContributionEventExpired from './contribution_event/contribution_event_expired.vue';
|
||||
|
|
@ -18,6 +19,7 @@ import ContributionEventPushed from './contribution_event/contribution_event_pus
|
|||
import ContributionEventPrivate from './contribution_event/contribution_event_private.vue';
|
||||
import ContributionEventMerged from './contribution_event/contribution_event_merged.vue';
|
||||
import ContributionEventCreated from './contribution_event/contribution_event_created.vue';
|
||||
import ContributionEventClosed from './contribution_event/contribution_event_closed.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -136,6 +138,9 @@ export default {
|
|||
case EVENT_TYPE_CREATED:
|
||||
return ContributionEventCreated;
|
||||
|
||||
case EVENT_TYPE_CLOSED:
|
||||
return ContributionEventClosed;
|
||||
|
||||
default:
|
||||
return EmptyComponent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export const TARGET_TYPE_DESIGN = 'DesignManagement::Design';
|
|||
export const TARGET_TYPE_WORK_ITEM = 'WorkItem';
|
||||
|
||||
// From app/models/work_items/type.rb#L28
|
||||
export const WORK_ITEM_ISSUE_TYPE_ISSUE = 'issue';
|
||||
export const WORK_ITEM_ISSUE_TYPE_TASK = 'task';
|
||||
export const WORK_ITEM_ISSUE_TYPE_INCIDENT = 'incident';
|
||||
|
||||
|
|
@ -38,9 +39,6 @@ export const EVENT_CREATED_I18N = {
|
|||
[TARGET_TYPE_MILESTONE]: s__(
|
||||
'ContributionEvent|Opened milestone %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
[TARGET_TYPE_ISSUE]: s__(
|
||||
'ContributionEvent|Opened issue %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
[TARGET_TYPE_MERGE_REQUEST]: s__(
|
||||
'ContributionEvent|Opened merge request %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
|
|
@ -50,6 +48,9 @@ export const EVENT_CREATED_I18N = {
|
|||
[TARGET_TYPE_DESIGN]: s__(
|
||||
'ContributionEvent|Added design %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
[WORK_ITEM_ISSUE_TYPE_ISSUE]: s__(
|
||||
'ContributionEvent|Opened issue %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
[WORK_ITEM_ISSUE_TYPE_TASK]: s__(
|
||||
'ContributionEvent|Opened task %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
|
|
@ -58,3 +59,28 @@ export const EVENT_CREATED_I18N = {
|
|||
),
|
||||
fallback: s__('ContributionEvent|Created resource.'),
|
||||
};
|
||||
|
||||
export const EVENT_CLOSED_I18N = {
|
||||
[TARGET_TYPE_MILESTONE]: s__(
|
||||
'ContributionEvent|Closed milestone %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
[TARGET_TYPE_MERGE_REQUEST]: s__(
|
||||
'ContributionEvent|Closed merge request %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
[WORK_ITEM_ISSUE_TYPE_ISSUE]: s__(
|
||||
'ContributionEvent|Closed issue %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
[WORK_ITEM_ISSUE_TYPE_TASK]: s__(
|
||||
'ContributionEvent|Closed task %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
[WORK_ITEM_ISSUE_TYPE_INCIDENT]: s__(
|
||||
'ContributionEvent|Closed incident %{targetLink} in %{resourceParentLink}.',
|
||||
),
|
||||
fallback: s__('ContributionEvent|Closed resource.'),
|
||||
};
|
||||
|
||||
export const EVENT_CLOSED_ICONS = {
|
||||
[WORK_ITEM_ISSUE_TYPE_ISSUE]: 'issue-closed',
|
||||
[TARGET_TYPE_MERGE_REQUEST]: 'merge-request-close',
|
||||
fallback: 'status_closed',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
|
||||
import { GlButton, GlModal, GlModalDirective, GlCard, GlIcon } from '@gitlab/ui';
|
||||
import { createAlert } from '~/alert';
|
||||
import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
|
||||
import { expandSection } from '~/settings_panels';
|
||||
|
|
@ -14,6 +14,8 @@ export default {
|
|||
BranchRule,
|
||||
GlButton,
|
||||
GlModal,
|
||||
GlCard,
|
||||
GlIcon,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
|
|
@ -55,29 +57,47 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="settings-content gl-mb-0">
|
||||
<branch-rule
|
||||
v-for="(rule, index) in branchRules"
|
||||
:key="`${rule.name}-${index}`"
|
||||
:name="rule.name"
|
||||
:is-default="rule.isDefault"
|
||||
:branch-protection="rule.branchProtection"
|
||||
:status-checks-total="rule.externalStatusChecks ? rule.externalStatusChecks.nodes.length : 0"
|
||||
:approval-rules-total="rule.approvalRules ? rule.approvalRules.nodes.length : 0"
|
||||
:matching-branches-count="rule.matchingBranchesCount"
|
||||
/>
|
||||
|
||||
<div v-if="!branchRules.length" data-testid="empty">{{ $options.i18n.emptyState }}</div>
|
||||
|
||||
<gl-button
|
||||
v-gl-modal="$options.modalId"
|
||||
class="gl-mt-5"
|
||||
data-qa-selector="add_branch_rule_button"
|
||||
category="secondary"
|
||||
variant="confirm"
|
||||
>{{ $options.i18n.addBranchRule }}</gl-button
|
||||
>
|
||||
|
||||
<gl-card
|
||||
class="gl-new-card gl-overflow-hidden"
|
||||
header-class="gl-new-card-header"
|
||||
body-class="gl-new-card-body gl-px-0"
|
||||
>
|
||||
<template #header>
|
||||
<div class="gl-new-card-title-wrapper" data-testid="title">
|
||||
<h3 class="gl-new-card-title">
|
||||
{{ __('Branch Rules') }}
|
||||
</h3>
|
||||
<div class="gl-new-card-count">
|
||||
<gl-icon name="branch" class="gl-mr-2" />
|
||||
{{ branchRules.length }}
|
||||
</div>
|
||||
</div>
|
||||
<gl-button
|
||||
v-gl-modal="$options.modalId"
|
||||
size="small"
|
||||
class="gl-ml-3"
|
||||
data-qa-selector="add_branch_rule_button"
|
||||
>{{ $options.i18n.addBranchRule }}</gl-button
|
||||
>
|
||||
</template>
|
||||
<ul class="content-list">
|
||||
<branch-rule
|
||||
v-for="(rule, index) in branchRules"
|
||||
:key="`${rule.name}-${index}`"
|
||||
:name="rule.name"
|
||||
:is-default="rule.isDefault"
|
||||
:branch-protection="rule.branchProtection"
|
||||
:status-checks-total="
|
||||
rule.externalStatusChecks ? rule.externalStatusChecks.nodes.length : 0
|
||||
"
|
||||
:approval-rules-total="rule.approvalRules ? rule.approvalRules.nodes.length : 0"
|
||||
:matching-branches-count="rule.matchingBranchesCount"
|
||||
class="gl-px-5! gl-py-4!"
|
||||
/>
|
||||
<div v-if="!branchRules.length" class="gl-new-card-empty gl-px-5 gl-py-4" data-testid="empty">
|
||||
{{ $options.i18n.emptyState }}
|
||||
</div>
|
||||
</ul>
|
||||
<gl-modal
|
||||
:ref="$options.modalId"
|
||||
:modal-id="$options.modalId"
|
||||
|
|
@ -88,5 +108,5 @@ export default {
|
|||
<p>{{ $options.i18n.branchRuleModalDescription }}</p>
|
||||
<p>{{ $options.i18n.branchRuleModalContent }}</p>
|
||||
</gl-modal>
|
||||
</div>
|
||||
</gl-card>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { getAccessLevels } from '../../../utils';
|
|||
export const i18n = {
|
||||
defaultLabel: s__('BranchRules|default'),
|
||||
protectedLabel: s__('BranchRules|protected'),
|
||||
detailsButtonLabel: s__('BranchRules|Details'),
|
||||
detailsButtonLabel: s__('BranchRules|View details'),
|
||||
allowForcePush: s__('BranchRules|Allowed to force push'),
|
||||
codeOwnerApprovalRequired: s__('BranchRules|Requires CODEOWNERS approval'),
|
||||
statusChecks: s__('BranchRules|%{total} status %{subject}'),
|
||||
|
|
@ -153,28 +153,36 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="gl-border-b gl-pt-5 gl-pb-5 gl-display-flex gl-justify-content-space-between"
|
||||
data-qa-selector="branch_content"
|
||||
:data-qa-branch-name="name"
|
||||
>
|
||||
<div>
|
||||
<strong class="gl-font-monospace">{{ name }}</strong>
|
||||
|
||||
<gl-badge v-if="isDefault" variant="info" size="sm" class="gl-ml-2">{{
|
||||
$options.i18n.defaultLabel
|
||||
}}</gl-badge>
|
||||
|
||||
<gl-badge v-if="isProtected" variant="success" size="sm" class="gl-ml-2">{{
|
||||
$options.i18n.protectedLabel
|
||||
}}</gl-badge>
|
||||
|
||||
<ul v-if="hasApprovalDetails" class="gl-pl-6 gl-mt-2 gl-mb-0 gl-text-gray-500">
|
||||
<li v-for="(detail, index) in approvalDetails" :key="index">{{ detail }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<gl-button class="gl-align-self-start" data-qa-selector="details_button" :href="detailsPath">
|
||||
{{ $options.i18n.detailsButtonLabel }}</gl-button
|
||||
<li>
|
||||
<div
|
||||
class="gl-display-flex gl-justify-content-space-between"
|
||||
data-qa-selector="branch_content"
|
||||
:data-qa-branch-name="name"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<strong class="gl-font-monospace">{{ name }}</strong>
|
||||
|
||||
<gl-badge v-if="isDefault" variant="info" size="sm" class="gl-ml-2">{{
|
||||
$options.i18n.defaultLabel
|
||||
}}</gl-badge>
|
||||
|
||||
<gl-badge v-if="isProtected" variant="success" size="sm" class="gl-ml-2">{{
|
||||
$options.i18n.protectedLabel
|
||||
}}</gl-badge>
|
||||
|
||||
<ul v-if="hasApprovalDetails" class="gl-pl-6 gl-mt-2 gl-mb-0 gl-text-gray-500">
|
||||
<li v-for="(detail, index) in approvalDetails" :key="index">{{ detail }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<gl-button
|
||||
class="gl-align-self-start"
|
||||
category="tertiary"
|
||||
size="small"
|
||||
data-qa-selector="details_button"
|
||||
:href="detailsPath"
|
||||
>
|
||||
{{ $options.i18n.detailsButtonLabel }}</gl-button
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ module Enums
|
|||
end
|
||||
|
||||
def self.purl_types
|
||||
PURL_TYPES
|
||||
# return 0 by default if the purl_type is not found, to prevent
|
||||
# consumers from producing invalid SQL caused by null entries
|
||||
@_purl_types ||= PURL_TYPES.dup.tap { |h| h.default = 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ module Profile
|
|||
expose(:id) { |event| event.target.id }
|
||||
expose(:target_type, as: :type)
|
||||
expose(:target_title, as: :title)
|
||||
expose(:issue_type, if: ->(event) { event.work_item? }) do |event|
|
||||
expose(:issue_type, if: ->(event) { event.work_item? || event.issue? }) do |event|
|
||||
event.target.issue_type
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@
|
|||
= _('Define rules for who can push, merge, and the required approvals for each branch.')
|
||||
= link_to(_('Leave feedback.'), 'https://gitlab.com/gitlab-org/gitlab/-/issues/388149', target: '_blank', rel: 'noopener noreferrer')
|
||||
|
||||
.settings-content.gl-pr-0
|
||||
.settings-content
|
||||
#js-branch-rules{ data: { project_path: @project.full_path, branch_rules_path: project_settings_repository_branch_rules_path(@project), show_code_owners: show_code_owners.to_s, show_status_checks: show_status_checks.to_s, show_approvers: show_approvers.to_s } }
|
||||
|
|
|
|||
|
|
@ -14,15 +14,24 @@ module Sbom
|
|||
'composer' => 'composer',
|
||||
'conan' => 'conan',
|
||||
'go' => 'golang',
|
||||
'gobinary' => 'golang', # this package manager is generated by trivy
|
||||
'nuget' => 'nuget',
|
||||
'pip' => 'pypi',
|
||||
'pipenv' => 'pypi',
|
||||
'setuptools' => 'pypi'
|
||||
'setuptools' => 'pypi',
|
||||
'python-pkg' => 'pypi' # this package manager is generated by trivy
|
||||
}.with_indifferent_access.freeze
|
||||
|
||||
def self.purl_type_for_pkg_manager(package_manager)
|
||||
matches = package_manager.match(TRIVY_PACKAGE_MANAGER_REGEX)
|
||||
|
||||
package_manager = matches['trivy-package-manager-type'] if matches
|
||||
|
||||
PACKAGE_MANAGER_TO_PURL_TYPE_MAP[package_manager]
|
||||
end
|
||||
|
||||
TRIVY_PACKAGE_MANAGER_REGEX = /\((?<trivy-package-manager-type>.*?)\)/
|
||||
private_constant :TRIVY_PACKAGE_MANAGER_REGEX
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7108,12 +7108,18 @@ msgstr ""
|
|||
msgid "BackgroundMigrations|Started at"
|
||||
msgstr ""
|
||||
|
||||
msgid "Badge"
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges"
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges|Add badge"
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges|Add new badge"
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges|Adding the badge failed, please check the entered URLs and try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -7132,6 +7138,9 @@ msgstr ""
|
|||
msgid "Badges|Deleting the badge failed, please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges|Edit badge"
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges|Enter a valid URL"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -7174,10 +7183,10 @@ msgstr ""
|
|||
msgid "Badges|The badge was deleted."
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges|This group has no badges"
|
||||
msgid "Badges|This group has no badges, start by creating a new one above."
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges|This project has no badges"
|
||||
msgid "Badges|This project has no badges, start by creating a new one above."
|
||||
msgstr ""
|
||||
|
||||
msgid "Badges|You are going to delete this badge. Deleted badges %{strongStart}cannot%{strongEnd} be restored."
|
||||
|
|
@ -8190,6 +8199,9 @@ msgstr ""
|
|||
msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Branch Rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "Branch already exists"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -8295,9 +8307,6 @@ msgstr ""
|
|||
msgid "BranchRules|Create wildcard: %{searchTerm}"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Details"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|Does not allow force push"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -8367,6 +8376,9 @@ msgstr ""
|
|||
msgid "BranchRules|Users"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|View details"
|
||||
msgstr ""
|
||||
|
||||
msgid "BranchRules|default"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13013,6 +13025,39 @@ msgstr ""
|
|||
msgid "ContributionEvent|Approved merge request %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed Epic %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed incident %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed issue %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed key result %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed merge request %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed milestone %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed objective %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed requirement %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed resource."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed task %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Closed test case %{targetLink} in %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributionEvent|Created project %{resourceParentLink}."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
"@gitlab/cluster-client": "^1.2.0",
|
||||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/fonts": "^1.3.0",
|
||||
"@gitlab/svgs": "3.58.0",
|
||||
"@gitlab/svgs": "3.59.0",
|
||||
"@gitlab/ui": "64.20.1",
|
||||
"@gitlab/visual-review-tools": "1.7.3",
|
||||
"@gitlab/web-ide": "0.0.1-dev-20230802205337",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module QA
|
|||
|
||||
view 'app/assets/javascripts/badges/components/badge_list.vue' do
|
||||
element :badge_list_content
|
||||
element :badge_list_row
|
||||
element :badge_list
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/badges/components/badge.vue' do
|
||||
|
|
@ -38,7 +38,7 @@ module QA
|
|||
|
||||
def has_badge?(badge_name)
|
||||
within_element(:badge_list_content) do
|
||||
has_element?(:badge_list_row, badge_name: badge_name)
|
||||
has_element?(:badge_list, badge_name: badge_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ RSpec.describe 'Group Badges', feature_category: :groups_and_projects do
|
|||
page.within '.badge-settings' do
|
||||
wait_for_requests
|
||||
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
expect(rows[0]).to have_content badge_1.link_url
|
||||
expect(rows[1]).to have_content badge_2.link_url
|
||||
|
|
@ -33,6 +33,7 @@ RSpec.describe 'Group Badges', feature_category: :groups_and_projects do
|
|||
|
||||
context 'adding a badge', :js do
|
||||
it 'user can preview a badge' do
|
||||
click_button 'Add badge'
|
||||
page.within '.badge-settings form' do
|
||||
fill_in 'badge-link-url', with: badge_link_url
|
||||
fill_in 'badge-image-url', with: badge_image_url
|
||||
|
|
@ -44,6 +45,7 @@ RSpec.describe 'Group Badges', feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
it do
|
||||
click_button 'Add badge'
|
||||
page.within '.badge-settings' do
|
||||
fill_in 'badge-link-url', with: badge_link_url
|
||||
fill_in 'badge-image-url', with: badge_image_url
|
||||
|
|
@ -51,7 +53,7 @@ RSpec.describe 'Group Badges', feature_category: :groups_and_projects do
|
|||
click_button 'Add badge'
|
||||
wait_for_requests
|
||||
|
||||
within '.card-body' do
|
||||
within '.gl-card-body' do
|
||||
expect(find('a')[:href]).to eq badge_link_url
|
||||
expect(find('a img')[:src]).to eq badge_image_url
|
||||
end
|
||||
|
|
@ -63,32 +65,35 @@ RSpec.describe 'Group Badges', feature_category: :groups_and_projects do
|
|||
it 'form is shown when clicking edit button in list' do
|
||||
page.within '.badge-settings' do
|
||||
wait_for_requests
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
rows[1].find('[aria-label="Edit"]').click
|
||||
end
|
||||
|
||||
within 'form' do
|
||||
expect(find('#badge-link-url').value).to eq badge_2.link_url
|
||||
expect(find('#badge-image-url').value).to eq badge_2.image_url
|
||||
end
|
||||
page.within '.gl-modal' do
|
||||
expect(find('#badge-link-url').value).to eq badge_2.link_url
|
||||
expect(find('#badge-image-url').value).to eq badge_2.image_url
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates a badge when submitting the edit form' do
|
||||
page.within '.badge-settings' do
|
||||
wait_for_requests
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
rows[1].find('[aria-label="Edit"]').click
|
||||
within 'form' do
|
||||
fill_in 'badge-link-url', with: badge_link_url
|
||||
fill_in 'badge-image-url', with: badge_image_url
|
||||
end
|
||||
|
||||
click_button 'Save changes'
|
||||
wait_for_requests
|
||||
end
|
||||
page.within '.gl-modal' do
|
||||
fill_in 'badge-link-url', with: badge_link_url
|
||||
fill_in 'badge-image-url', with: badge_image_url
|
||||
|
||||
rows = all('.card-body > div')
|
||||
click_button 'Save changes'
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
page.within '.badge-settings' do
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
expect(rows[1]).to have_content badge_link_url
|
||||
end
|
||||
|
|
@ -102,7 +107,7 @@ RSpec.describe 'Group Badges', feature_category: :groups_and_projects do
|
|||
|
||||
it 'shows a modal when deleting a badge' do
|
||||
wait_for_requests
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
|
||||
click_delete_button(rows[1])
|
||||
|
|
@ -112,14 +117,14 @@ RSpec.describe 'Group Badges', feature_category: :groups_and_projects do
|
|||
|
||||
it 'deletes a badge when confirming the modal' do
|
||||
wait_for_requests
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
click_delete_button(rows[1])
|
||||
|
||||
find('.modal .btn-danger').click
|
||||
wait_for_requests
|
||||
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 1
|
||||
expect(rows[0]).to have_content badge_1.link_url
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ RSpec.describe 'Project Badges', feature_category: :groups_and_projects do
|
|||
page.within '.badge-settings' do
|
||||
wait_for_requests
|
||||
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
expect(rows[0]).to have_content group_badge.link_url
|
||||
expect(rows[1]).to have_content project_badge.link_url
|
||||
|
|
@ -33,6 +33,7 @@ RSpec.describe 'Project Badges', feature_category: :groups_and_projects do
|
|||
|
||||
context 'adding a badge', :js do
|
||||
it 'user can preview a badge' do
|
||||
click_button 'Add badge'
|
||||
page.within '.badge-settings form' do
|
||||
fill_in 'badge-link-url', with: badge_link_url
|
||||
fill_in 'badge-image-url', with: badge_image_url
|
||||
|
|
@ -44,6 +45,7 @@ RSpec.describe 'Project Badges', feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
it do
|
||||
click_button 'Add badge'
|
||||
page.within '.badge-settings' do
|
||||
fill_in 'badge-link-url', with: badge_link_url
|
||||
fill_in 'badge-image-url', with: badge_image_url
|
||||
|
|
@ -51,7 +53,7 @@ RSpec.describe 'Project Badges', feature_category: :groups_and_projects do
|
|||
click_button 'Add badge'
|
||||
wait_for_requests
|
||||
|
||||
within '.card-body' do
|
||||
within '.gl-card-body' do
|
||||
expect(find('a')[:href]).to eq badge_link_url
|
||||
expect(find('a img')[:src]).to eq badge_image_url
|
||||
end
|
||||
|
|
@ -63,32 +65,35 @@ RSpec.describe 'Project Badges', feature_category: :groups_and_projects do
|
|||
it 'form is shown when clicking edit button in list' do
|
||||
page.within '.badge-settings' do
|
||||
wait_for_requests
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
rows[1].find('[aria-label="Edit"]').click
|
||||
end
|
||||
|
||||
within 'form' do
|
||||
expect(find('#badge-link-url').value).to eq project_badge.link_url
|
||||
expect(find('#badge-image-url').value).to eq project_badge.image_url
|
||||
end
|
||||
page.within '.gl-modal' do
|
||||
expect(find('#badge-link-url').value).to eq project_badge.link_url
|
||||
expect(find('#badge-image-url').value).to eq project_badge.image_url
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates a badge when submitting the edit form' do
|
||||
page.within '.badge-settings' do
|
||||
wait_for_requests
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
rows[1].find('[aria-label="Edit"]').click
|
||||
within 'form' do
|
||||
fill_in 'badge-link-url', with: badge_link_url
|
||||
fill_in 'badge-image-url', with: badge_image_url
|
||||
end
|
||||
|
||||
click_button 'Save changes'
|
||||
wait_for_requests
|
||||
end
|
||||
page.within '.gl-modal' do
|
||||
fill_in 'badge-link-url', with: badge_link_url
|
||||
fill_in 'badge-image-url', with: badge_image_url
|
||||
|
||||
rows = all('.card-body > div')
|
||||
click_button 'Save changes'
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
page.within '.badge-settings' do
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
expect(rows[1]).to have_content badge_link_url
|
||||
end
|
||||
|
|
@ -102,7 +107,7 @@ RSpec.describe 'Project Badges', feature_category: :groups_and_projects do
|
|||
|
||||
it 'shows a modal when deleting a badge' do
|
||||
wait_for_requests
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
|
||||
click_delete_button(rows[1])
|
||||
|
|
@ -112,14 +117,14 @@ RSpec.describe 'Project Badges', feature_category: :groups_and_projects do
|
|||
|
||||
it 'deletes a badge when confirming the modal' do
|
||||
wait_for_requests
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 2
|
||||
click_delete_button(rows[1])
|
||||
|
||||
find('.modal .btn-danger').click
|
||||
wait_for_requests
|
||||
|
||||
rows = all('.card-body > div')
|
||||
rows = all('.gl-card-body tbody tr')
|
||||
expect(rows.length).to eq 1
|
||||
expect(rows[0]).to have_content group_badge.link_url
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe('BadgeForm component', () => {
|
|||
it('stops editing when cancel button is clicked', async () => {
|
||||
createComponent({ isEditing: true });
|
||||
|
||||
const cancelButton = wrapper.find('.row-content-block button');
|
||||
const cancelButton = wrapper.findAll('[data-testid="action-buttons"] button').at(1);
|
||||
|
||||
await cancelButton.trigger('click');
|
||||
|
||||
|
|
@ -143,13 +143,13 @@ describe('BadgeForm component', () => {
|
|||
describe('if isEditing is false', () => {
|
||||
const props = { isEditing: false };
|
||||
|
||||
it('renders one button', () => {
|
||||
it('renders two buttons', () => {
|
||||
createComponent(props);
|
||||
|
||||
expect(wrapper.find('.row-content-block').exists()).toBe(false);
|
||||
const buttons = wrapper.findAll('.form-group:last-of-type button');
|
||||
const buttons = wrapper.findAll('[data-testid="action-buttons"] button');
|
||||
|
||||
expect(buttons).toHaveLength(1);
|
||||
expect(buttons).toHaveLength(2);
|
||||
const buttonAddWrapper = buttons.at(0);
|
||||
|
||||
expect(buttonAddWrapper.isVisible()).toBe(true);
|
||||
|
|
@ -164,15 +164,15 @@ describe('BadgeForm component', () => {
|
|||
|
||||
it('renders two buttons', () => {
|
||||
createComponent(props);
|
||||
const buttons = wrapper.findAll('.row-content-block button');
|
||||
const buttons = wrapper.findAll('[data-testid="action-buttons"] button');
|
||||
|
||||
expect(buttons).toHaveLength(2);
|
||||
|
||||
const saveButton = buttons.at(1);
|
||||
const saveButton = buttons.at(0);
|
||||
expect(saveButton.isVisible()).toBe(true);
|
||||
expect(saveButton.text()).toBe('Save changes');
|
||||
|
||||
const cancelButton = buttons.at(0);
|
||||
const cancelButton = buttons.at(1);
|
||||
expect(cancelButton.isVisible()).toBe(true);
|
||||
expect(cancelButton.text()).toBe('Cancel');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import BadgeListRow from '~/badges/components/badge_list_row.vue';
|
||||
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
|
||||
|
||||
import createState from '~/badges/store/state';
|
||||
import mutations from '~/badges/store/mutations';
|
||||
import actions from '~/badges/store/actions';
|
||||
|
||||
import { createDummyBadge } from '../dummy_badge';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
describe('BadgeListRow component', () => {
|
||||
let badge;
|
||||
let wrapper;
|
||||
let mockedActions;
|
||||
|
||||
const createComponent = (kind) => {
|
||||
setHTMLFixture(`<div id="delete-badge-modal" class="modal"></div>`);
|
||||
|
||||
mockedActions = Object.fromEntries(Object.keys(actions).map((name) => [name, jest.fn()]));
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
...createState(),
|
||||
kind: PROJECT_BADGE,
|
||||
},
|
||||
mutations,
|
||||
actions: mockedActions,
|
||||
});
|
||||
|
||||
badge = createDummyBadge();
|
||||
badge.kind = kind;
|
||||
wrapper = mount(BadgeListRow, {
|
||||
attachTo: document.body,
|
||||
store,
|
||||
propsData: { badge },
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
resetHTMLFixture();
|
||||
});
|
||||
|
||||
describe('for a project badge', () => {
|
||||
beforeEach(() => {
|
||||
createComponent(PROJECT_BADGE);
|
||||
});
|
||||
|
||||
it('renders the badge', () => {
|
||||
const badgeImage = wrapper.find('.project-badge');
|
||||
|
||||
expect(badgeImage.exists()).toBe(true);
|
||||
expect(badgeImage.attributes('src')).toBe(badge.renderedImageUrl);
|
||||
});
|
||||
|
||||
it('renders the badge name', () => {
|
||||
expect(wrapper.text()).toMatch(badge.name);
|
||||
});
|
||||
|
||||
it('renders the badge link', () => {
|
||||
expect(wrapper.text()).toMatch(badge.linkUrl);
|
||||
});
|
||||
|
||||
it('renders the badge kind', () => {
|
||||
expect(wrapper.text()).toMatch('Project Badge');
|
||||
});
|
||||
|
||||
it('shows edit and delete buttons', () => {
|
||||
const buttons = wrapper.findAll('.table-button-footer button');
|
||||
|
||||
expect(buttons).toHaveLength(2);
|
||||
const editButton = buttons.at(0);
|
||||
|
||||
expect(editButton.isVisible()).toBe(true);
|
||||
expect(editButton.element).toHaveSpriteIcon('pencil');
|
||||
|
||||
const deleteButton = buttons.at(1);
|
||||
expect(deleteButton.isVisible()).toBe(true);
|
||||
expect(deleteButton.element).toHaveSpriteIcon('remove');
|
||||
});
|
||||
|
||||
it('calls editBadge when clicking then edit button', async () => {
|
||||
const editButton = wrapper.find('.table-button-footer button:first-of-type');
|
||||
|
||||
await editButton.trigger('click');
|
||||
|
||||
expect(mockedActions.editBadge).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls updateBadgeInModal and shows modal when clicking then delete button', async () => {
|
||||
const deleteButton = wrapper.find('.table-button-footer button:last-of-type');
|
||||
|
||||
await deleteButton.trigger('click');
|
||||
|
||||
expect(mockedActions.updateBadgeInModal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for a group badge', () => {
|
||||
beforeEach(() => {
|
||||
createComponent(GROUP_BADGE);
|
||||
});
|
||||
|
||||
it('renders the badge kind', () => {
|
||||
expect(wrapper.text()).toMatch('Group Badge');
|
||||
});
|
||||
|
||||
it('hides edit and delete buttons', () => {
|
||||
const buttons = wrapper.findAll('.table-button-footer button');
|
||||
|
||||
expect(buttons).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
import { GlTable, GlButton } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import BadgeList from '~/badges/components/badge_list.vue';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
|
||||
|
||||
import createState from '~/badges/store/state';
|
||||
import mutations from '~/badges/store/mutations';
|
||||
import actions from '~/badges/store/actions';
|
||||
|
||||
import BadgeList from '~/badges/components/badge_list.vue';
|
||||
import { createDummyBadge } from '../dummy_badge';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
|
@ -21,9 +19,16 @@ const badges = Array.from({ length: numberOfDummyBadges }).map((_, idx) => ({
|
|||
|
||||
describe('BadgeList component', () => {
|
||||
let wrapper;
|
||||
let mockedActions;
|
||||
|
||||
const findTable = () => wrapper.findComponent(GlTable);
|
||||
const findTableRow = (pos) => findTable().find('tbody').findAll('tr').at(pos);
|
||||
const findButtons = () => wrapper.findByTestId('badge-actions').findAllComponents(GlButton);
|
||||
const findEditButton = () => wrapper.findByTestId('edit-badge-button');
|
||||
const findDeleteButton = () => wrapper.findByTestId('delete-badge');
|
||||
|
||||
const createComponent = (customState) => {
|
||||
const mockedActions = Object.fromEntries(Object.keys(actions).map((name) => [name, jest.fn()]));
|
||||
mockedActions = Object.fromEntries(Object.keys(actions).map((name) => [name, jest.fn()]));
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
|
|
@ -35,28 +40,23 @@ describe('BadgeList component', () => {
|
|||
actions: mockedActions,
|
||||
});
|
||||
|
||||
wrapper = mount(BadgeList, { store });
|
||||
wrapper = mountExtended(BadgeList, {
|
||||
store,
|
||||
stubs: {
|
||||
GlTable,
|
||||
GlButton,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('for project badges', () => {
|
||||
it('renders a header with the badge count', () => {
|
||||
createComponent({
|
||||
kind: PROJECT_BADGE,
|
||||
badges,
|
||||
});
|
||||
|
||||
const header = wrapper.find('.card-header');
|
||||
|
||||
expect(header.text()).toMatchInterpolatedText('Your badges 3');
|
||||
});
|
||||
|
||||
it('renders a row for each badge', () => {
|
||||
createComponent({
|
||||
kind: PROJECT_BADGE,
|
||||
badges,
|
||||
});
|
||||
|
||||
const rows = wrapper.findAll('.gl-responsive-table-row');
|
||||
const rows = findTable().find('tbody').findAll('tr');
|
||||
|
||||
expect(rows).toHaveLength(numberOfDummyBadges);
|
||||
});
|
||||
|
|
@ -89,4 +89,60 @@ describe('BadgeList component', () => {
|
|||
expect(wrapper.text()).toMatch('This group has no badges');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BadgeList item', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
kind: PROJECT_BADGE,
|
||||
badges,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the badge', () => {
|
||||
const badgeImage = wrapper.find('.project-badge');
|
||||
|
||||
expect(badgeImage.exists()).toBe(true);
|
||||
expect(badgeImage.attributes('src')).toBe(badges[0].renderedImageUrl);
|
||||
});
|
||||
|
||||
it('renders the badge name', () => {
|
||||
const badgeCell = findTableRow(0).findAll('td').at(0);
|
||||
|
||||
expect(badgeCell.text()).toMatch(badges[0].name);
|
||||
});
|
||||
|
||||
it('renders the badge link', () => {
|
||||
expect(wrapper.text()).toMatch(badges[0].linkUrl);
|
||||
});
|
||||
|
||||
it('renders the badge kind', () => {
|
||||
expect(wrapper.text()).toMatch('Project Badge');
|
||||
});
|
||||
|
||||
it('shows edit and delete buttons', () => {
|
||||
expect(findButtons()).toHaveLength(2);
|
||||
|
||||
const editButton = findEditButton();
|
||||
|
||||
expect(editButton.isVisible()).toBe(true);
|
||||
expect(editButton.element).toHaveSpriteIcon('pencil');
|
||||
|
||||
const deleteButton = findDeleteButton();
|
||||
|
||||
expect(deleteButton.isVisible()).toBe(true);
|
||||
expect(deleteButton.element).toHaveSpriteIcon('remove');
|
||||
});
|
||||
|
||||
it('calls editBadge when clicking then edit button', () => {
|
||||
findEditButton().trigger('click');
|
||||
|
||||
expect(mockedActions.editBadge).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls updateBadgeInModal and shows modal when clicking then delete button', () => {
|
||||
findDeleteButton().trigger('click');
|
||||
|
||||
expect(mockedActions.updateBadgeInModal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { GlModal } from '@gitlab/ui';
|
||||
import { GlCard, GlTable } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import BadgeList from '~/badges/components/badge_list.vue';
|
||||
import BadgeListRow from '~/badges/components/badge_list_row.vue';
|
||||
import BadgeSettings from '~/badges/components/badge_settings.vue';
|
||||
import BadgeList from '~/badges/components/badge_list.vue';
|
||||
import BadgeForm from '~/badges/components/badge_form.vue';
|
||||
import store from '~/badges/store';
|
||||
import { createDummyBadge } from '../dummy_badge';
|
||||
|
||||
|
|
@ -22,8 +22,10 @@ describe('BadgeSettings component', () => {
|
|||
wrapper = shallowMount(BadgeSettings, {
|
||||
store,
|
||||
stubs: {
|
||||
GlCard,
|
||||
GlTable,
|
||||
'badge-list': BadgeList,
|
||||
'badge-list-row': BadgeListRow,
|
||||
'badge-form': BadgeForm,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -32,35 +34,35 @@ describe('BadgeSettings component', () => {
|
|||
createComponent();
|
||||
});
|
||||
|
||||
it('displays modal if button for deleting a badge is clicked', async () => {
|
||||
const button = wrapper.find('[data-testid="delete-badge"]');
|
||||
it('renders a header with the badge count', () => {
|
||||
createComponent();
|
||||
|
||||
button.vm.$emit('click');
|
||||
await nextTick();
|
||||
const cardTitle = wrapper.find('.gl-new-card-title');
|
||||
const cardCount = wrapper.find('.gl-new-card-count');
|
||||
|
||||
const modal = wrapper.findComponent(GlModal);
|
||||
expect(modal.isVisible()).toBe(true);
|
||||
expect(cardTitle.text()).toContain('Your badges');
|
||||
expect(cardCount.text()).toContain('1');
|
||||
});
|
||||
|
||||
it('displays a form to add a badge', () => {
|
||||
expect(wrapper.find('[data-testid="add-new-badge"]').isVisible()).toBe(true);
|
||||
it('displays a table', () => {
|
||||
expect(wrapper.findComponent(GlTable).isVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays badge list', () => {
|
||||
it('renders badge add form', () => {
|
||||
expect(wrapper.findComponent(BadgeForm).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders badge list', () => {
|
||||
expect(wrapper.findComponent(BadgeList).isVisible()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when editing', () => {
|
||||
beforeEach(() => {
|
||||
createComponent(true);
|
||||
createComponent({ isEditing: true });
|
||||
});
|
||||
|
||||
it('displays a form to edit a badge', () => {
|
||||
expect(wrapper.find('[data-testid="edit-badge"]').isVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays no badge list', () => {
|
||||
expect(wrapper.findComponent(BadgeList).isVisible()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import ContributionEventClosed from '~/contribution_events/components/contribution_event/contribution_event_closed.vue';
|
||||
import ContributionEventBase from '~/contribution_events/components/contribution_event/contribution_event_base.vue';
|
||||
import { TARGET_TYPE_WORK_ITEM } from '~/contribution_events/constants';
|
||||
import {
|
||||
eventMilestoneClosed,
|
||||
eventIssueClosed,
|
||||
eventMergeRequestClosed,
|
||||
eventTaskClosed,
|
||||
eventIncidentClosed,
|
||||
} from '../../utils';
|
||||
|
||||
describe('ContributionEventClosed', () => {
|
||||
let wrapper;
|
||||
|
||||
const createComponent = ({ propsData }) => {
|
||||
wrapper = shallowMountExtended(ContributionEventClosed, {
|
||||
propsData,
|
||||
});
|
||||
};
|
||||
|
||||
describe.each`
|
||||
event | expectedMessage | iconName | iconClass
|
||||
${eventMilestoneClosed()} | ${'Closed milestone %{targetLink} in %{resourceParentLink}.'} | ${'status_closed'} | ${'gl-text-blue-500'}
|
||||
${eventIssueClosed()} | ${'Closed issue %{targetLink} in %{resourceParentLink}.'} | ${'issue-closed'} | ${'gl-text-blue-500'}
|
||||
${eventMergeRequestClosed()} | ${'Closed merge request %{targetLink} in %{resourceParentLink}.'} | ${'merge-request-close'} | ${'gl-text-red-500'}
|
||||
${{ target: { type: 'unsupported type' } }} | ${'Closed resource.'} | ${'status_closed'} | ${'gl-text-blue-500'}
|
||||
`(
|
||||
'when event target type is $event.target.type',
|
||||
({ event, expectedMessage, iconName, iconClass }) => {
|
||||
it('renders `ContributionEventBase` with correct props', () => {
|
||||
createComponent({ propsData: { event } });
|
||||
|
||||
expect(wrapper.findComponent(ContributionEventBase).props()).toMatchObject({
|
||||
event,
|
||||
message: expectedMessage,
|
||||
iconName,
|
||||
iconClass,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe(`when event target type is ${TARGET_TYPE_WORK_ITEM}`, () => {
|
||||
describe.each`
|
||||
event | expectedMessage
|
||||
${eventTaskClosed()} | ${'Closed task %{targetLink} in %{resourceParentLink}.'}
|
||||
${eventIncidentClosed()} | ${'Closed incident %{targetLink} in %{resourceParentLink}.'}
|
||||
${{ target: { type: TARGET_TYPE_WORK_ITEM, issue_type: 'unsupported type' } }} | ${'Closed resource.'}
|
||||
`('when issue type is $event.target.issue_type', ({ event, expectedMessage }) => {
|
||||
it('renders `ContributionEventBase` with correct props', () => {
|
||||
createComponent({ propsData: { event } });
|
||||
|
||||
expect(wrapper.findComponent(ContributionEventBase).props()).toMatchObject({
|
||||
event,
|
||||
message: expectedMessage,
|
||||
iconName: 'status_closed',
|
||||
iconClass: 'gl-text-blue-500',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -8,6 +8,7 @@ import ContributionEventPushed from '~/contribution_events/components/contributi
|
|||
import ContributionEventPrivate from '~/contribution_events/components/contribution_event/contribution_event_private.vue';
|
||||
import ContributionEventMerged from '~/contribution_events/components/contribution_event/contribution_event_merged.vue';
|
||||
import ContributionEventCreated from '~/contribution_events/components/contribution_event/contribution_event_created.vue';
|
||||
import ContributionEventClosed from '~/contribution_events/components/contribution_event/contribution_event_closed.vue';
|
||||
import {
|
||||
eventApproved,
|
||||
eventExpired,
|
||||
|
|
@ -17,6 +18,7 @@ import {
|
|||
eventPrivate,
|
||||
eventMerged,
|
||||
eventCreated,
|
||||
eventClosed,
|
||||
} from '../utils';
|
||||
|
||||
describe('ContributionEvents', () => {
|
||||
|
|
@ -34,6 +36,7 @@ describe('ContributionEvents', () => {
|
|||
eventPrivate(),
|
||||
eventMerged(),
|
||||
eventCreated(),
|
||||
eventClosed(),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
@ -49,6 +52,7 @@ describe('ContributionEvents', () => {
|
|||
${ContributionEventPrivate} | ${eventPrivate()}
|
||||
${ContributionEventMerged} | ${eventMerged()}
|
||||
${ContributionEventCreated} | ${eventCreated()}
|
||||
${ContributionEventClosed} | ${eventClosed()}
|
||||
`(
|
||||
'renders `$expectedComponent.name` component and passes expected event',
|
||||
({ expectedComponent, expectedEvent }) => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
EVENT_TYPE_PUSHED,
|
||||
EVENT_TYPE_PRIVATE,
|
||||
EVENT_TYPE_MERGED,
|
||||
EVENT_TYPE_CLOSED,
|
||||
PUSH_EVENT_REF_TYPE_BRANCH,
|
||||
PUSH_EVENT_REF_TYPE_TAG,
|
||||
EVENT_TYPE_CREATED,
|
||||
|
|
@ -21,6 +22,15 @@ import {
|
|||
} from '~/contribution_events/constants';
|
||||
|
||||
const findEventByAction = (action) => () => events.find((event) => event.action === action);
|
||||
const findEventByActionAndTargetType = (action, targetType) => () =>
|
||||
events.find((event) => event.action === action && event.target?.type === targetType);
|
||||
const findEventByActionAndIssueType = (action, issueType) => () =>
|
||||
events.find(
|
||||
(event) =>
|
||||
event.action === action &&
|
||||
event.target?.type === TARGET_TYPE_WORK_ITEM &&
|
||||
event.target.issue_type === issueType,
|
||||
);
|
||||
|
||||
export const eventApproved = findEventByAction(EVENT_TYPE_APPROVED);
|
||||
|
||||
|
|
@ -62,15 +72,10 @@ export const eventPrivate = () => ({ ...events[0], action: EVENT_TYPE_PRIVATE })
|
|||
|
||||
export const eventCreated = findEventByAction(EVENT_TYPE_CREATED);
|
||||
|
||||
export const findCreatedEvent = (targetType) => () =>
|
||||
events.find((event) => event.action === EVENT_TYPE_CREATED && event.target?.type === targetType);
|
||||
export const findWorkItemCreatedEvent = (issueType) => () =>
|
||||
events.find(
|
||||
(event) =>
|
||||
event.action === EVENT_TYPE_CREATED &&
|
||||
event.target?.type === TARGET_TYPE_WORK_ITEM &&
|
||||
event.target.issue_type === issueType,
|
||||
);
|
||||
export const findCreatedEvent = (targetType) =>
|
||||
findEventByActionAndTargetType(EVENT_TYPE_CREATED, targetType);
|
||||
export const findWorkItemCreatedEvent = (issueType) =>
|
||||
findEventByActionAndIssueType(EVENT_TYPE_CREATED, issueType);
|
||||
|
||||
export const eventProjectCreated = findCreatedEvent(undefined);
|
||||
export const eventMilestoneCreated = findCreatedEvent(TARGET_TYPE_MILESTONE);
|
||||
|
|
@ -80,3 +85,18 @@ export const eventWikiPageCreated = findCreatedEvent(TARGET_TYPE_WIKI);
|
|||
export const eventDesignCreated = findCreatedEvent(TARGET_TYPE_DESIGN);
|
||||
export const eventTaskCreated = findWorkItemCreatedEvent(WORK_ITEM_ISSUE_TYPE_TASK);
|
||||
export const eventIncidentCreated = findWorkItemCreatedEvent(WORK_ITEM_ISSUE_TYPE_INCIDENT);
|
||||
|
||||
export const eventClosed = findEventByAction(EVENT_TYPE_CLOSED);
|
||||
|
||||
export const findClosedEvent = (targetType) =>
|
||||
findEventByActionAndTargetType(EVENT_TYPE_CREATED, targetType);
|
||||
export const findWorkItemClosedEvent = (issueType) =>
|
||||
findEventByActionAndIssueType(EVENT_TYPE_CLOSED, issueType);
|
||||
|
||||
export const eventMilestoneClosed = findClosedEvent(TARGET_TYPE_MILESTONE);
|
||||
export const eventIssueClosed = findClosedEvent(TARGET_TYPE_ISSUE);
|
||||
export const eventMergeRequestClosed = findClosedEvent(TARGET_TYPE_MERGE_REQUEST);
|
||||
export const eventWikiPageClosed = findClosedEvent(TARGET_TYPE_WIKI);
|
||||
export const eventDesignClosed = findClosedEvent(TARGET_TYPE_DESIGN);
|
||||
export const eventTaskClosed = findWorkItemClosedEvent(WORK_ITEM_ISSUE_TYPE_TASK);
|
||||
export const eventIncidentClosed = findWorkItemClosedEvent(WORK_ITEM_ISSUE_TYPE_INCIDENT);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Sbom::PurlType::Converter, feature_category: :dependency_management do
|
||||
describe '.purl_type_for_pkg_manager' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
subject(:actual_purl_type) { described_class.purl_type_for_pkg_manager(package_manager) }
|
||||
|
||||
where(:given_package_manager, :expected_purl_type) do
|
||||
'bundler' | 'gem'
|
||||
'yarn' | 'npm'
|
||||
'npm' | 'npm'
|
||||
'pnpm' | 'npm'
|
||||
'maven' | 'maven'
|
||||
'sbt' | 'maven'
|
||||
'gradle' | 'maven'
|
||||
'composer' | 'composer'
|
||||
'conan' | 'conan'
|
||||
'go' | 'golang'
|
||||
'nuget' | 'nuget'
|
||||
'pip' | 'pypi'
|
||||
'pipenv' | 'pypi'
|
||||
'setuptools' | 'pypi'
|
||||
'Python (python-pkg)' | 'pypi'
|
||||
'analyzer (gobinary)' | 'golang'
|
||||
'unknown-pkg-manager' | nil
|
||||
'Python (unknown)' | nil
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:package_manager) { given_package_manager }
|
||||
|
||||
it 'returns the expected purl_type' do
|
||||
expect(actual_purl_type).to eql(expected_purl_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Enums::Sbom, feature_category: :dependency_management do
|
||||
describe '.purl_types' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
subject(:actual_purl_type) { described_class.purl_types[package_manager] }
|
||||
|
||||
where(:given_package_manager, :expected_purl_type) do
|
||||
:composer | 1
|
||||
'composer' | 1
|
||||
:conan | 2
|
||||
'conan' | 2
|
||||
:gem | 3
|
||||
:golang | 4
|
||||
:maven | 5
|
||||
:npm | 6
|
||||
:nuget | 7
|
||||
:pypi | 8
|
||||
:apk | 9
|
||||
:rpm | 10
|
||||
:deb | 11
|
||||
:cbl_mariner | 12
|
||||
'unknown-pkg-manager' | 0
|
||||
'Python (unknown)' | 0
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:package_manager) { given_package_manager }
|
||||
|
||||
it 'returns the expected purl_type' do
|
||||
expect(actual_purl_type).to eql(expected_purl_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -140,6 +140,17 @@ RSpec.describe Profile::EventEntity, feature_category: :user_profile do
|
|||
expect(subject[:target][:issue_type]).to eq('incident')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when target is an issue' do
|
||||
let(:issue) { build_stubbed(:issue, author: target_user, project: project) }
|
||||
let(:event) do
|
||||
build(:event, :created, author: target_user, project: project, target: issue)
|
||||
end
|
||||
|
||||
it 'exposes `issue_type`' do
|
||||
expect(subject[:target][:issue_type]).to eq('issue')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with resource parent' do
|
||||
|
|
|
|||
|
|
@ -34,11 +34,18 @@ RSpec.shared_context 'with user contribution events' do
|
|||
# closed
|
||||
let_it_be(:closed_issue_event) { create(:event, :closed, author: user, project: project, target: issue) }
|
||||
let_it_be(:closed_milestone_event) { create(:event, :closed, author: user, project: project, target: milestone) }
|
||||
let_it_be(:closed_incident_event) { create(:event, :closed, author: user, project: project, target: incident) }
|
||||
let_it_be(:closed_merge_request_event) do
|
||||
create(:event, :closed, author: user, project: project, target: merge_request)
|
||||
end
|
||||
|
||||
let_it_be(:closed_task_event) do
|
||||
create(:event, :closed, :for_work_item, author: user, project: project, target: task)
|
||||
end
|
||||
|
||||
let_it_be(:closed_incident_event) do
|
||||
create(:event, :closed, :for_work_item, author: user, project: project, target: incident)
|
||||
end
|
||||
|
||||
# commented
|
||||
let_it_be(:commented_event) do
|
||||
create(:event, :commented, author: user, project: project, target: note_on_issue)
|
||||
|
|
|
|||
|
|
@ -1127,10 +1127,10 @@
|
|||
stylelint-declaration-strict-value "1.8.0"
|
||||
stylelint-scss "4.2.0"
|
||||
|
||||
"@gitlab/svgs@3.58.0":
|
||||
version "3.58.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.58.0.tgz#cae8483c81e260af6d1d55a25235099683ed76b7"
|
||||
integrity sha512-4aCsp0sVn+XBYJAiO/7IdwVxfINBJ0bRvvuvM1R91KYjs2XFw/rtg1HPQ+9MxZHcD5x/cIdDL6dWwr3XzfFWjw==
|
||||
"@gitlab/svgs@3.59.0":
|
||||
version "3.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.59.0.tgz#21090154aa7987e059264e13182c4c60e6d0d4b3"
|
||||
integrity sha512-5+FZ0Clwtf2X6oHEEVCwbhqhmnxT8Ds1CGFxHzzWsvQ5Hkdt658BVAicsbvQSU+TuEIhnKOK3BfooyleMUwLlQ==
|
||||
|
||||
"@gitlab/ui@64.20.1":
|
||||
version "64.20.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue