Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d081e00aa7
commit
4c5468b408
|
@ -336,7 +336,7 @@ export default {
|
|||
:sidebar-collapsed="sidebarCollapsed"
|
||||
@alert-refresh="alertRefresh"
|
||||
@toggle-sidebar="toggleSidebar"
|
||||
@alert-sidebar-error="handleAlertSidebarError"
|
||||
@alert-error="handleAlertSidebarError"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,8 +6,6 @@ import {
|
|||
GlTable,
|
||||
GlAlert,
|
||||
GlIcon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlLink,
|
||||
GlTabs,
|
||||
GlTab,
|
||||
|
@ -16,12 +14,13 @@ import {
|
|||
GlSearchBoxByType,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import createFlash from '~/flash';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { debounce, trim } from 'lodash';
|
||||
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { convertToSnakeCase } from '~/lib/utils/text_utility';
|
||||
import Tracking from '~/tracking';
|
||||
import getAlerts from '../graphql/queries/get_alerts.query.graphql';
|
||||
import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
|
||||
import {
|
||||
|
@ -31,9 +30,7 @@ import {
|
|||
trackAlertListViewsOptions,
|
||||
trackAlertStatusUpdateOptions,
|
||||
} from '../constants';
|
||||
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
|
||||
import { convertToSnakeCase } from '~/lib/utils/text_utility';
|
||||
import Tracking from '~/tracking';
|
||||
import AlertStatus from './alert_status.vue';
|
||||
|
||||
const tdClass = 'table-col gl-display-flex d-md-table-cell gl-align-items-center';
|
||||
const thClass = 'gl-hover-bg-blue-50';
|
||||
|
@ -107,11 +104,6 @@ export default {
|
|||
sortable: true,
|
||||
},
|
||||
],
|
||||
statuses: {
|
||||
TRIGGERED: s__('AlertManagement|Triggered'),
|
||||
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
|
||||
RESOLVED: s__('AlertManagement|Resolved'),
|
||||
},
|
||||
severityLabels: ALERTS_SEVERITY_LABELS,
|
||||
statusTabs: ALERTS_STATUS_TABS,
|
||||
components: {
|
||||
|
@ -121,8 +113,6 @@ export default {
|
|||
GlAlert,
|
||||
GlDeprecatedButton,
|
||||
TimeAgo,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlTabs,
|
||||
|
@ -131,6 +121,7 @@ export default {
|
|||
GlPagination,
|
||||
GlSearchBoxByType,
|
||||
GlSprintf,
|
||||
AlertStatus,
|
||||
},
|
||||
props: {
|
||||
projectPath: {
|
||||
|
@ -204,6 +195,7 @@ export default {
|
|||
return {
|
||||
searchTerm: '',
|
||||
errored: false,
|
||||
errorMessage: '',
|
||||
isAlertDismissed: false,
|
||||
isErrorAlertDismissed: false,
|
||||
sort: 'STARTED_AT_DESC',
|
||||
|
@ -275,30 +267,6 @@ export default {
|
|||
this.searchTerm = trimmedInput;
|
||||
}
|
||||
}, 500),
|
||||
updateAlertStatus(status, iid) {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updateAlertStatus,
|
||||
variables: {
|
||||
iid,
|
||||
status: status.toUpperCase(),
|
||||
projectPath: this.projectPath,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.trackStatusUpdate(status);
|
||||
this.$apollo.queries.alerts.refetch();
|
||||
this.$apollo.queries.alertsCount.refetch();
|
||||
this.resetPagination();
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash(
|
||||
s__(
|
||||
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
navigateToAlertDetails({ iid }) {
|
||||
return visitUrl(joinPaths(window.location.pathname, iid, 'details'));
|
||||
},
|
||||
|
@ -338,6 +306,14 @@ export default {
|
|||
resetPagination() {
|
||||
this.pagination = initialPaginationState;
|
||||
},
|
||||
handleAlertError(errorMessage) {
|
||||
this.errored = true;
|
||||
this.errorMessage = errorMessage;
|
||||
},
|
||||
dismissError() {
|
||||
this.isErrorAlertDismissed = true;
|
||||
this.errorMessage = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -357,8 +333,13 @@ export default {
|
|||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
<gl-alert v-if="showErrorMsg" variant="danger" @dismiss="isErrorAlertDismissed = true">
|
||||
{{ $options.i18n.errorMsg }}
|
||||
<gl-alert
|
||||
v-if="showErrorMsg"
|
||||
variant="danger"
|
||||
data-testid="alert-error"
|
||||
@dismiss="dismissError"
|
||||
>
|
||||
{{ errorMessage || $options.i18n.errorMsg }}
|
||||
</gl-alert>
|
||||
|
||||
<gl-tabs content-class="gl-p-0" @input="filterAlertsByStatus">
|
||||
|
@ -437,22 +418,12 @@ export default {
|
|||
</template>
|
||||
|
||||
<template #cell(status)="{ item }">
|
||||
<gl-dropdown :text="$options.statuses[item.status]" class="w-100" right>
|
||||
<gl-dropdown-item
|
||||
v-for="(label, field) in $options.statuses"
|
||||
:key="field"
|
||||
@click="updateAlertStatus(label, item.iid)"
|
||||
>
|
||||
<span class="d-flex">
|
||||
<gl-icon
|
||||
class="flex-shrink-0 append-right-4"
|
||||
:class="{ invisible: label.toUpperCase() !== item.status }"
|
||||
name="mobile-issue-close"
|
||||
/>
|
||||
{{ label }}
|
||||
</span>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
<alert-status
|
||||
:alert="item"
|
||||
:project-path="projectPath"
|
||||
:is-sidebar="false"
|
||||
@alert-error="handleAlertError"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
|
|
|
@ -49,7 +49,7 @@ export default {
|
|||
:project-path="projectPath"
|
||||
:alert="alert"
|
||||
@toggle-sidebar="$emit('toggle-sidebar')"
|
||||
@alert-sidebar-error="$emit('alert-sidebar-error', $event)"
|
||||
@alert-error="$emit('alert-error', $event)"
|
||||
/>
|
||||
<sidebar-assignees
|
||||
:project-path="projectPath"
|
||||
|
@ -58,7 +58,7 @@ export default {
|
|||
:sidebar-collapsed="sidebarCollapsed"
|
||||
@alert-refresh="$emit('alert-refresh')"
|
||||
@toggle-sidebar="$emit('toggle-sidebar')"
|
||||
@alert-sidebar-error="$emit('alert-sidebar-error', $event)"
|
||||
@alert-error="$emit('alert-error', $event)"
|
||||
/>
|
||||
<div class="block"></div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import { trackAlertStatusUpdateOptions } from '../constants';
|
||||
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
|
||||
|
||||
export default {
|
||||
statuses: {
|
||||
TRIGGERED: s__('AlertManagement|Triggered'),
|
||||
ACKNOWLEDGED: s__('AlertManagement|Acknowledged'),
|
||||
RESOLVED: s__('AlertManagement|Resolved'),
|
||||
},
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlButton,
|
||||
},
|
||||
props: {
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
alert: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isDropdownShowing: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
isSidebar: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dropdownClass() {
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
return this.isSidebar ? (this.isDropdownShowing ? 'show' : 'gl-display-none') : '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateAlertStatus(status) {
|
||||
this.$emit('handle-updating', true);
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updateAlertStatus,
|
||||
variables: {
|
||||
iid: this.alert.iid,
|
||||
status: status.toUpperCase(),
|
||||
projectPath: this.projectPath,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.trackStatusUpdate(status);
|
||||
this.$emit('hide-dropdown');
|
||||
})
|
||||
.catch(() => {
|
||||
this.$emit(
|
||||
'alert-error',
|
||||
s__(
|
||||
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
|
||||
),
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
this.$emit('handle-updating', false);
|
||||
});
|
||||
},
|
||||
trackStatusUpdate(status) {
|
||||
const { category, action, label } = trackAlertStatusUpdateOptions;
|
||||
Tracking.event(category, action, { label, property: status });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
|
||||
<gl-dropdown
|
||||
ref="dropdown"
|
||||
right
|
||||
:text="$options.statuses[alert.status]"
|
||||
class="w-100"
|
||||
toggle-class="dropdown-menu-toggle"
|
||||
variant="outline-default"
|
||||
@keydown.esc.native="$emit('hide-dropdown')"
|
||||
@hide="$emit('hide-dropdown')"
|
||||
>
|
||||
<div class="dropdown-title text-center">
|
||||
<span class="alert-title">{{ s__('AlertManagement|Assign status') }}</span>
|
||||
<gl-button
|
||||
:aria-label="__('Close')"
|
||||
variant="link"
|
||||
class="dropdown-title-button dropdown-menu-close"
|
||||
icon="close"
|
||||
@click="$emit('hide-dropdown')"
|
||||
/>
|
||||
</div>
|
||||
<div class="dropdown-content dropdown-body">
|
||||
<gl-dropdown-item
|
||||
v-for="(label, field) in $options.statuses"
|
||||
:key="field"
|
||||
data-testid="statusDropdownItem"
|
||||
class="gl-vertical-align-middle"
|
||||
:active="label.toUpperCase() === alert.status"
|
||||
:active-class="'is-active'"
|
||||
@click="updateAlertStatus(label)"
|
||||
>
|
||||
{{ label }}
|
||||
</gl-dropdown-item>
|
||||
</div>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
</template>
|
|
@ -142,7 +142,7 @@ export default {
|
|||
this.users = data;
|
||||
})
|
||||
.catch(() => {
|
||||
this.$emit('alert-sidebar-error', this.$options.FETCH_USERS_ERROR);
|
||||
this.$emit('alert-error', this.$options.FETCH_USERS_ERROR);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isDropdownSearching = false;
|
||||
|
@ -172,7 +172,7 @@ export default {
|
|||
return this.$emit('alert-refresh');
|
||||
})
|
||||
.catch(() => {
|
||||
this.$emit('alert-sidebar-error', this.$options.UPDATE_ALERT_ASSIGNEES_ERROR);
|
||||
this.$emit('alert-error', this.$options.UPDATE_ALERT_ASSIGNEES_ERROR);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isUpdating = false;
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
<script>
|
||||
import {
|
||||
GlIcon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlLoadingIcon,
|
||||
GlTooltip,
|
||||
GlButton,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { GlIcon, GlLoadingIcon, GlTooltip, GlSprintf } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import Tracking from '~/tracking';
|
||||
import { trackAlertStatusUpdateOptions } from '../../constants';
|
||||
import updateAlertStatus from '../../graphql/mutations/update_alert_status.graphql';
|
||||
import AlertStatus from '../alert_status.vue';
|
||||
|
||||
export default {
|
||||
statuses: {
|
||||
|
@ -21,12 +11,10 @@ export default {
|
|||
},
|
||||
components: {
|
||||
GlIcon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlLoadingIcon,
|
||||
GlTooltip,
|
||||
GlButton,
|
||||
GlSprintf,
|
||||
AlertStatus,
|
||||
},
|
||||
props: {
|
||||
projectPath: {
|
||||
|
@ -60,44 +48,13 @@ export default {
|
|||
},
|
||||
toggleFormDropdown() {
|
||||
this.isDropdownShowing = !this.isDropdownShowing;
|
||||
const { dropdown } = this.$refs.dropdown.$refs;
|
||||
const { dropdown } = this.$children[2].$refs.dropdown.$refs;
|
||||
if (dropdown && this.isDropdownShowing) {
|
||||
dropdown.show();
|
||||
}
|
||||
},
|
||||
isSelected(status) {
|
||||
return this.alert.status === status;
|
||||
},
|
||||
updateAlertStatus(status) {
|
||||
this.isUpdating = true;
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updateAlertStatus,
|
||||
variables: {
|
||||
iid: this.alert.iid,
|
||||
status: status.toUpperCase(),
|
||||
projectPath: this.projectPath,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
this.trackStatusUpdate(status);
|
||||
this.hideDropdown();
|
||||
})
|
||||
.catch(() => {
|
||||
this.$emit(
|
||||
'alert-sidebar-error',
|
||||
s__(
|
||||
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
|
||||
),
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isUpdating = false;
|
||||
});
|
||||
},
|
||||
trackStatusUpdate(status) {
|
||||
const { category, action, label } = trackAlertStatusUpdateOptions;
|
||||
Tracking.event(category, action, { label, property: status });
|
||||
handleUpdating(updating) {
|
||||
this.isUpdating = updating;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -132,41 +89,15 @@ export default {
|
|||
</a>
|
||||
</p>
|
||||
|
||||
<div class="dropdown dropdown-menu-selectable" :class="dropdownClass">
|
||||
<gl-dropdown
|
||||
ref="dropdown"
|
||||
:text="$options.statuses[alert.status]"
|
||||
class="w-100"
|
||||
toggle-class="dropdown-menu-toggle"
|
||||
variant="outline-default"
|
||||
@keydown.esc.native="hideDropdown"
|
||||
@hide="hideDropdown"
|
||||
>
|
||||
<div class="dropdown-title">
|
||||
<span class="alert-title">{{ s__('AlertManagement|Assign status') }}</span>
|
||||
<gl-button
|
||||
:aria-label="__('Close')"
|
||||
variant="link"
|
||||
class="dropdown-title-button dropdown-menu-close"
|
||||
icon="close"
|
||||
@click="hideDropdown"
|
||||
/>
|
||||
</div>
|
||||
<div class="dropdown-content dropdown-body">
|
||||
<gl-dropdown-item
|
||||
v-for="(label, field) in $options.statuses"
|
||||
:key="field"
|
||||
data-testid="statusDropdownItem"
|
||||
class="gl-vertical-align-middle"
|
||||
:active="label.toUpperCase() === alert.status"
|
||||
:active-class="'is-active'"
|
||||
@click="updateAlertStatus(label)"
|
||||
>
|
||||
{{ label }}
|
||||
</gl-dropdown-item>
|
||||
</div>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
<alert-status
|
||||
:alert="alert"
|
||||
:project-path="projectPath"
|
||||
:is-dropdown-showing="isDropdownShowing"
|
||||
:is-sidebar="true"
|
||||
@alert-error="$emit('alert-error', $event)"
|
||||
@hide-dropdown="hideDropdown"
|
||||
@handle-updating="handleUpdating"
|
||||
/>
|
||||
|
||||
<gl-loading-icon v-if="isUpdating" :inline="true" />
|
||||
<p
|
||||
|
|
|
@ -1,4 +1,87 @@
|
|||
import Vue from 'vue';
|
||||
/**
|
||||
* An event hub with a Vue instance like API
|
||||
*
|
||||
* NOTE: There's an [issue open][4] to eventually remove this when some
|
||||
* coupling in our codebase has been fixed.
|
||||
*
|
||||
* NOTE: This is a derivative work from [mitt][1] v1.2.0 which is licensed by
|
||||
* [MIT License][2] © [Jason Miller][3]
|
||||
*
|
||||
* [1]: https://github.com/developit/mitt
|
||||
* [2]: https://opensource.org/licenses/MIT
|
||||
* [3]: https://jasonformat.com/
|
||||
* [4]: https://gitlab.com/gitlab-org/gitlab/-/issues/223864
|
||||
*/
|
||||
class EventHub {
|
||||
constructor() {
|
||||
this.$_all = new Map();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.$_all.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event handler for the given type.
|
||||
*
|
||||
* @param {string|symbol} type Type of event to listen for
|
||||
* @param {Function} handler Function to call in response to given event
|
||||
*/
|
||||
$on(type, handler) {
|
||||
const handlers = this.$_all.get(type);
|
||||
const added = handlers && handlers.push(handler);
|
||||
|
||||
if (!added) {
|
||||
this.$_all.set(type, [handler]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an event handler or all handlers for the given type.
|
||||
*
|
||||
* @param {string|symbol} type Type of event to unregister `handler`
|
||||
* @param {Function} handler Handler function to remove
|
||||
*/
|
||||
$off(type, handler) {
|
||||
const handlers = this.$_all.get(type) || [];
|
||||
|
||||
const newHandlers = handler ? handlers.filter(x => x !== handler) : [];
|
||||
|
||||
if (newHandlers.length) {
|
||||
this.$_all.set(type, newHandlers);
|
||||
} else {
|
||||
this.$_all.delete(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener to type but only trigger it once
|
||||
*
|
||||
* @param {string|symbol} type Type of event to listen for
|
||||
* @param {Function} handler Handler function to call in response to event
|
||||
*/
|
||||
$once(type, handler) {
|
||||
const wrapHandler = (...args) => {
|
||||
this.$off(type, wrapHandler);
|
||||
handler(...args);
|
||||
};
|
||||
this.$on(type, wrapHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke all handlers for the given type.
|
||||
*
|
||||
* @param {string|symbol} type The event type to invoke
|
||||
* @param {Any} [evt] Any value passed to each handler
|
||||
*/
|
||||
$emit(type, ...args) {
|
||||
const handlers = this.$_all.get(type) || [];
|
||||
|
||||
handlers.forEach(handler => {
|
||||
handler(...args);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Vue like event hub
|
||||
|
@ -14,5 +97,5 @@ import Vue from 'vue';
|
|||
* We'd like to shy away from using a full fledged Vue instance from this in the future.
|
||||
*/
|
||||
export default () => {
|
||||
return new Vue();
|
||||
return new EventHub();
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export const BYTES_IN_KIB = 1024;
|
||||
export const HIDDEN_CLASS = 'hidden';
|
||||
export const TRUNCATE_WIDTH_DEFAULT_WIDTH = 80;
|
||||
export const TRUNCATE_WIDTH_DEFAULT_FONT_SIZE = 12;
|
||||
|
||||
export const DATETIME_RANGE_TYPES = {
|
||||
fixed: 'fixed',
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import { isString } from 'lodash';
|
||||
import { isString, memoize } from 'lodash';
|
||||
|
||||
import {
|
||||
TRUNCATE_WIDTH_DEFAULT_WIDTH,
|
||||
TRUNCATE_WIDTH_DEFAULT_FONT_SIZE,
|
||||
} from '~/lib/utils/constants';
|
||||
|
||||
/**
|
||||
* Adds a , to a string composed by numbers, at every 3 chars.
|
||||
|
@ -73,7 +78,79 @@ export const slugifyWithUnderscore = str => slugify(str, '_');
|
|||
* @param {Number} maxLength
|
||||
* @returns {String}
|
||||
*/
|
||||
export const truncate = (string, maxLength) => `${string.substr(0, maxLength - 3)}...`;
|
||||
export const truncate = (string, maxLength) => {
|
||||
if (string.length - 1 > maxLength) {
|
||||
return `${string.substr(0, maxLength - 1)}…`;
|
||||
}
|
||||
|
||||
return string;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function calculates the average char width. It does so by placing a string in the DOM and measuring the width.
|
||||
* NOTE: This will cause a reflow and should be used sparsely!
|
||||
* The default fontFamily is 'sans-serif' and 12px in ECharts, so that is the default basis for calculating the average with.
|
||||
* https://echarts.apache.org/en/option.html#xAxis.nameTextStyle.fontFamily
|
||||
* https://echarts.apache.org/en/option.html#xAxis.nameTextStyle.fontSize
|
||||
* @param {Object} options
|
||||
* @param {Number} options.fontSize style to size the text for measurement
|
||||
* @param {String} options.fontFamily style of font family to measure the text with
|
||||
* @param {String} options.chars string of chars to use as a basis for calculating average width
|
||||
* @return {Number}
|
||||
*/
|
||||
const getAverageCharWidth = memoize(function getAverageCharWidth(options = {}) {
|
||||
const {
|
||||
fontSize = 12,
|
||||
fontFamily = 'sans-serif',
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
chars = ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
||||
} = options;
|
||||
const div = document.createElement('div');
|
||||
|
||||
div.style.fontFamily = fontFamily;
|
||||
div.style.fontSize = `${fontSize}px`;
|
||||
// Place outside of view
|
||||
div.style.position = 'absolute';
|
||||
div.style.left = -1000;
|
||||
div.style.top = -1000;
|
||||
|
||||
div.innerHTML = chars;
|
||||
|
||||
document.body.appendChild(div);
|
||||
const width = div.clientWidth;
|
||||
document.body.removeChild(div);
|
||||
|
||||
return width / chars.length / fontSize;
|
||||
});
|
||||
|
||||
/**
|
||||
* This function returns a truncated version of `string` if its estimated rendered width is longer than `maxWidth`,
|
||||
* otherwise it will return the original `string`
|
||||
* Inspired by https://bl.ocks.org/tophtucker/62f93a4658387bb61e4510c37e2e97cf
|
||||
* @param {String} string text to truncate
|
||||
* @param {Object} options
|
||||
* @param {Number} options.maxWidth largest rendered width the text may have
|
||||
* @param {Number} options.fontSize size of the font used to render the text
|
||||
* @return {String} either the original string or a truncated version
|
||||
*/
|
||||
export const truncateWidth = (string, options = {}) => {
|
||||
const {
|
||||
maxWidth = TRUNCATE_WIDTH_DEFAULT_WIDTH,
|
||||
fontSize = TRUNCATE_WIDTH_DEFAULT_FONT_SIZE,
|
||||
} = options;
|
||||
const { truncateIndex } = string.split('').reduce(
|
||||
(memo, char, index) => {
|
||||
let newIndex = index;
|
||||
if (memo.width > maxWidth) {
|
||||
newIndex = memo.truncateIndex;
|
||||
}
|
||||
return { width: memo.width + getAverageCharWidth() * fontSize, truncateIndex: newIndex };
|
||||
},
|
||||
{ width: 0, truncateIndex: 0 },
|
||||
);
|
||||
|
||||
return truncate(string, truncateIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* Truncate SHA to 8 characters
|
||||
|
|
|
@ -32,7 +32,7 @@ export default class AbuseReports {
|
|||
$messageCellElement.text(originalMessage);
|
||||
} else {
|
||||
$messageCellElement.data('messageTruncated', 'true');
|
||||
$messageCellElement.text(`${originalMessage.substr(0, MAX_MESSAGE_LENGTH - 3)}...`);
|
||||
$messageCellElement.text(truncate(originalMessage, MAX_MESSAGE_LENGTH));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import Icon from '~/vue_shared/components/icon.vue';
|
|||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { ASSET_LINK_TYPE } from '../constants';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { difference } from 'lodash';
|
||||
import { difference, get } from 'lodash';
|
||||
|
||||
export default {
|
||||
name: 'ReleaseBlockAssets',
|
||||
|
@ -54,7 +54,7 @@ export default {
|
|||
sections() {
|
||||
return [
|
||||
{
|
||||
links: this.assets.sources.map(s => ({
|
||||
links: get(this.assets, 'sources', []).map(s => ({
|
||||
url: s.url,
|
||||
name: sprintf(__('Source code (%{fileExtension})'), { fileExtension: s.format }),
|
||||
})),
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
content: none !important;
|
||||
}
|
||||
|
||||
div {
|
||||
div:not(.dropdown-title) {
|
||||
width: 100% !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class MilestoneStatsType < BaseObject
|
||||
graphql_name 'MilestoneStats'
|
||||
description 'Contains statistics about a milestone'
|
||||
|
||||
authorize :read_milestone
|
||||
|
||||
field :total_issues_count, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Total number of issues associated with the milestone'
|
||||
|
||||
field :closed_issues_count, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Number of closed issues associated with the milestone'
|
||||
end
|
||||
end
|
|
@ -9,6 +9,8 @@ module Types
|
|||
|
||||
authorize :read_milestone
|
||||
|
||||
alias_method :milestone, :object
|
||||
|
||||
field :id, GraphQL::ID_TYPE, null: false,
|
||||
description: 'ID of the milestone'
|
||||
|
||||
|
@ -47,5 +49,14 @@ module Types
|
|||
field :subgroup_milestone, GraphQL::BOOLEAN_TYPE, null: false,
|
||||
description: 'Indicates if milestone is at subgroup level',
|
||||
method: :subgroup_milestone?
|
||||
|
||||
field :stats, Types::MilestoneStatsType, null: true,
|
||||
description: 'Milestone statistics'
|
||||
|
||||
def stats
|
||||
return unless Feature.enabled?(:graphql_milestone_stats, milestone.project || milestone.group, default_enabled: true)
|
||||
|
||||
milestone
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve the performance for loading large diffs on a Merge request
|
||||
merge_request: 33037
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add ref, released_at, milestones to release yml
|
||||
merge_request: 34943
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expose ref, milestones, released_at to releaser-cli
|
||||
merge_request: 35115
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add milestone stats to GraphQL endpoint
|
||||
merge_request: 35066
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix release assets for Guest users of private projects
|
||||
merge_request: 35166
|
||||
author:
|
||||
type: fixed
|
|
@ -7676,6 +7676,11 @@ type Milestone {
|
|||
"""
|
||||
state: MilestoneStateEnum!
|
||||
|
||||
"""
|
||||
Milestone statistics
|
||||
"""
|
||||
stats: MilestoneStats
|
||||
|
||||
"""
|
||||
Indicates if milestone is at subgroup level
|
||||
"""
|
||||
|
@ -7737,6 +7742,21 @@ enum MilestoneStateEnum {
|
|||
closed
|
||||
}
|
||||
|
||||
"""
|
||||
Contains statistics about a milestone
|
||||
"""
|
||||
type MilestoneStats {
|
||||
"""
|
||||
Number of closed issues associated with the milestone
|
||||
"""
|
||||
closedIssuesCount: Int
|
||||
|
||||
"""
|
||||
Total number of issues associated with the milestone
|
||||
"""
|
||||
totalIssuesCount: Int
|
||||
}
|
||||
|
||||
"""
|
||||
The position to which the adjacent object should be moved
|
||||
"""
|
||||
|
|
|
@ -21487,6 +21487,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "stats",
|
||||
"description": "Milestone statistics",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "MilestoneStats",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "subgroupMilestone",
|
||||
"description": "Indicates if milestone is at subgroup level",
|
||||
|
@ -21702,6 +21716,47 @@
|
|||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MilestoneStats",
|
||||
"description": "Contains statistics about a milestone",
|
||||
"fields": [
|
||||
{
|
||||
"name": "closedIssuesCount",
|
||||
"description": "Number of closed issues associated with the milestone",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "totalIssuesCount",
|
||||
"description": "Total number of issues associated with the milestone",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "MoveType",
|
||||
|
|
|
@ -1180,11 +1180,21 @@ Represents a milestone.
|
|||
| `projectMilestone` | Boolean! | Indicates if milestone is at project level |
|
||||
| `startDate` | Time | Timestamp of the milestone start date |
|
||||
| `state` | MilestoneStateEnum! | State of the milestone |
|
||||
| `stats` | MilestoneStats | Milestone statistics |
|
||||
| `subgroupMilestone` | Boolean! | Indicates if milestone is at subgroup level |
|
||||
| `title` | String! | Title of the milestone |
|
||||
| `updatedAt` | Time! | Timestamp of last milestone update |
|
||||
| `webPath` | String! | Web path of the milestone |
|
||||
|
||||
## MilestoneStats
|
||||
|
||||
Contains statistics about a milestone
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `closedIssuesCount` | Int | Number of closed issues associated with the milestone |
|
||||
| `totalIssuesCount` | Int | Total number of issues associated with the milestone |
|
||||
|
||||
## Namespace
|
||||
|
||||
| Name | Type | Description |
|
||||
|
|
|
@ -386,9 +386,12 @@ author.
|
|||
- Learning how to find the right balance takes time; that is why we have
|
||||
reviewers that become maintainers after some time spent on reviewing merge
|
||||
requests.
|
||||
- Finding bugs and improving code style is important, but thinking about good
|
||||
design is important as well. Building abstractions and good design is what
|
||||
makes it possible to hide complexity and makes future changes easier.
|
||||
- Finding bugs is important, but thinking about good design is important as
|
||||
well. Building abstractions and good design is what makes it possible to hide
|
||||
complexity and makes future changes easier.
|
||||
- Enforcing and improving [code style](contributing/style_guides.md) should be primarily done through
|
||||
[automation](https://about.gitlab.com/handbook/values/#cleanup-over-sign-off)
|
||||
instead of review comments.
|
||||
- Asking the author to change the design sometimes means the complete rewrite
|
||||
of the contributed code. It's usually a good idea to ask another maintainer or
|
||||
reviewer before doing it, but have the courage to do it when you believe it is
|
||||
|
|
|
@ -616,8 +616,7 @@ From the project's settings:
|
|||
To allow or deny a license:
|
||||
|
||||
1. Either use the **Manage licenses** button in the merge request widget, or
|
||||
navigate to the project's **Settings > CI/CD** and expand the
|
||||
**License Compliance** section.
|
||||
select **Security & Compliance > License Compliance** in the project's sidebar navigation.
|
||||
1. Click the **Add a license** button.
|
||||
|
||||
![License Compliance Add License](img/license_compliance_add_license_v13_0.png)
|
||||
|
|
|
@ -6,9 +6,6 @@ module ContainerRegistry
|
|||
|
||||
attr_reader :repository, :name
|
||||
|
||||
# https://github.com/docker/distribution/commit/3150937b9f2b1b5b096b2634d0e7c44d4a0f89fb
|
||||
TAG_NAME_REGEX = /^[\w][\w.-]{0,127}$/.freeze
|
||||
|
||||
delegate :registry, :client, to: :repository
|
||||
delegate :revision, :short_revision, to: :config_blob, allow_nil: true
|
||||
|
||||
|
@ -16,10 +13,6 @@ module ContainerRegistry
|
|||
@repository, @name = repository, name
|
||||
end
|
||||
|
||||
def valid_name?
|
||||
!name.match(TAG_NAME_REGEX).nil?
|
||||
end
|
||||
|
||||
def valid?
|
||||
manifest.present?
|
||||
end
|
||||
|
|
|
@ -5,6 +5,8 @@ module Gitlab
|
|||
module Build
|
||||
class Releaser
|
||||
BASE_COMMAND = 'release-cli create'
|
||||
SINGLE_FLAGS = %i[name description tag_name ref released_at].freeze
|
||||
ARRAY_FLAGS = %i[milestones].freeze
|
||||
|
||||
attr_reader :config
|
||||
|
||||
|
@ -14,10 +16,21 @@ module Gitlab
|
|||
|
||||
def script
|
||||
command = BASE_COMMAND.dup
|
||||
config.each { |k, v| command.concat(" --#{k.to_s.dasherize} \"#{v}\"") }
|
||||
single_flags.each { |k, v| command.concat(" --#{k.to_s.dasherize} \"#{v}\"") }
|
||||
array_commands.each { |k, v| v.each { |elem| command.concat(" --#{k.to_s.singularize.dasherize} \"#{elem}\"") } }
|
||||
|
||||
[command]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def single_flags
|
||||
config.slice(*SINGLE_FLAGS)
|
||||
end
|
||||
|
||||
def array_commands
|
||||
config.slice(*ARRAY_FLAGS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,8 +12,9 @@ module Gitlab
|
|||
include ::Gitlab::Config::Entry::Validatable
|
||||
include ::Gitlab::Config::Entry::Attributable
|
||||
|
||||
ALLOWED_KEYS = %i[tag_name name description assets].freeze
|
||||
attributes %i[tag_name name assets].freeze
|
||||
ALLOWED_KEYS = %i[tag_name name description ref released_at milestones assets].freeze
|
||||
attributes %i[tag_name name ref milestones assets].freeze
|
||||
attr_reader :released_at
|
||||
|
||||
# Attributable description conflicts with
|
||||
# ::Gitlab::Config::Entry::Node.description
|
||||
|
@ -29,8 +30,25 @@ module Gitlab
|
|||
|
||||
validations do
|
||||
validates :config, allowed_keys: ALLOWED_KEYS
|
||||
validates :tag_name, presence: true
|
||||
validates :tag_name, type: String, presence: true
|
||||
validates :description, type: String, presence: true
|
||||
validates :milestones, array_of_strings_or_string: true, allow_blank: true
|
||||
validate do
|
||||
next unless config[:released_at]
|
||||
|
||||
begin
|
||||
@released_at = DateTime.iso8601(config[:released_at])
|
||||
rescue ArgumentError
|
||||
errors.add(:released_at, "must be a valid datetime")
|
||||
end
|
||||
end
|
||||
validate do
|
||||
next unless config[:ref]
|
||||
|
||||
unless Commit.reference_valid?(config[:ref])
|
||||
errors.add(:ref, "must be a valid ref")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def value
|
||||
|
|
|
@ -18,7 +18,6 @@ license_scanning:
|
|||
name: "$SECURE_ANALYZERS_PREFIX/license-finder:$LICENSE_MANAGEMENT_VERSION"
|
||||
entrypoint: [""]
|
||||
variables:
|
||||
LM_REPORT_FILE: gl-license-scanning-report.json
|
||||
LM_REPORT_VERSION: '2.1'
|
||||
SETUP_CMD: $LICENSE_MANAGEMENT_SETUP_CMD
|
||||
allow_failure: true
|
||||
|
@ -26,7 +25,7 @@ license_scanning:
|
|||
- /run.sh analyze .
|
||||
artifacts:
|
||||
reports:
|
||||
license_scanning: $LM_REPORT_FILE
|
||||
license_scanning: gl-license-scanning-report.json
|
||||
dependencies: []
|
||||
rules:
|
||||
- if: $LICENSE_MANAGEMENT_DISABLED
|
||||
|
|
|
@ -230,11 +230,15 @@ module Gitlab
|
|||
end
|
||||
|
||||
def added_lines
|
||||
@stats&.additions || diff_lines.count(&:added?)
|
||||
strong_memoize(:added_lines) do
|
||||
@stats&.additions || diff_lines.count(&:added?)
|
||||
end
|
||||
end
|
||||
|
||||
def removed_lines
|
||||
@stats&.deletions || diff_lines.count(&:removed?)
|
||||
strong_memoize(:removed_lines) do
|
||||
@stats&.deletions || diff_lines.count(&:removed?)
|
||||
end
|
||||
end
|
||||
|
||||
def file_identifier
|
||||
|
|
|
@ -88,15 +88,18 @@ module Gitlab
|
|||
|
||||
def diff_stats_collection
|
||||
strong_memoize(:diff_stats) do
|
||||
# There are scenarios where we don't need to request Diff Stats,
|
||||
# when caching for instance.
|
||||
next unless @include_stats
|
||||
next unless diff_refs
|
||||
next unless fetch_diff_stats?
|
||||
|
||||
@repository.diff_stats(diff_refs.base_sha, diff_refs.head_sha)
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_diff_stats?
|
||||
# There are scenarios where we don't need to request Diff Stats,
|
||||
# when caching for instance.
|
||||
@include_stats && diff_refs
|
||||
end
|
||||
|
||||
def decorate_diff!(diff)
|
||||
return diff if diff.is_a?(File)
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ module Gitlab
|
|||
strong_memoize(:diff_files) do
|
||||
diff_files = super
|
||||
|
||||
diff_files.each { |diff_file| cache.decorate(diff_file) }
|
||||
diff_files.each { |diff_file| highlight_cache.decorate(diff_file) }
|
||||
|
||||
diff_files
|
||||
end
|
||||
|
@ -28,16 +28,14 @@ module Gitlab
|
|||
|
||||
override :write_cache
|
||||
def write_cache
|
||||
cache.write_if_empty
|
||||
highlight_cache.write_if_empty
|
||||
diff_stats_cache&.write_if_empty(diff_stats_collection)
|
||||
end
|
||||
|
||||
override :clear_cache
|
||||
def clear_cache
|
||||
cache.clear
|
||||
end
|
||||
|
||||
def cache_key
|
||||
cache.key
|
||||
highlight_cache.clear
|
||||
diff_stats_cache&.clear
|
||||
end
|
||||
|
||||
def real_size
|
||||
|
@ -46,8 +44,27 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def cache
|
||||
@cache ||= Gitlab::Diff::HighlightCache.new(self)
|
||||
def highlight_cache
|
||||
strong_memoize(:highlight_cache) do
|
||||
Gitlab::Diff::HighlightCache.new(self)
|
||||
end
|
||||
end
|
||||
|
||||
def diff_stats_cache
|
||||
strong_memoize(:diff_stats_cache) do
|
||||
if Feature.enabled?(:cache_diff_stats_merge_request, project)
|
||||
Gitlab::Diff::StatsCache.new(cachable_key: @merge_request_diff.cache_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
override :diff_stats_collection
|
||||
def diff_stats_collection
|
||||
strong_memoize(:diff_stats) do
|
||||
next unless fetch_diff_stats?
|
||||
|
||||
diff_stats_cache&.read || super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
#
|
||||
module Gitlab
|
||||
module Diff
|
||||
class StatsCache
|
||||
include Gitlab::Metrics::Methods
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
EXPIRATION = 1.week
|
||||
VERSION = 1
|
||||
|
||||
def initialize(cachable_key:)
|
||||
@cachable_key = cachable_key
|
||||
end
|
||||
|
||||
def read
|
||||
strong_memoize(:cached_values) do
|
||||
content = cache.fetch(key)
|
||||
|
||||
next unless content
|
||||
|
||||
stats = content.map { |stat| Gitaly::DiffStats.new(stat) }
|
||||
|
||||
Gitlab::Git::DiffStatsCollection.new(stats)
|
||||
end
|
||||
end
|
||||
|
||||
def write_if_empty(stats)
|
||||
return if cache.exist?(key)
|
||||
return unless stats
|
||||
|
||||
cache.write(key, stats.as_json, expires_in: EXPIRATION)
|
||||
end
|
||||
|
||||
def clear
|
||||
cache.delete(key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :cachable_key
|
||||
|
||||
def cache
|
||||
Rails.cache
|
||||
end
|
||||
|
||||
def key
|
||||
strong_memoize(:redis_key) do
|
||||
['diff_stats', cachable_key, VERSION].join(":")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -24656,9 +24656,6 @@ msgstr ""
|
|||
msgid "UsageQuota|Storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|Storage usage:"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|This namespace has no projects which use shared runners"
|
||||
msgstr ""
|
||||
|
||||
|
@ -24689,6 +24686,12 @@ msgstr ""
|
|||
msgid "UsageQuota|Wikis"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|You used: %{usage} %{limit}"
|
||||
msgstr ""
|
||||
|
||||
msgid "UsageQuota|out of %{formattedLimit} of your namespace storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Use %{code_start}::%{code_end} to create a %{link_start}scoped label set%{link_end} (eg. %{code_start}priority::1%{code_end})"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import createFlash from '~/flash';
|
||||
import AlertManagementList from '~/alert_management/components/alert_management_list.vue';
|
||||
import {
|
||||
ALERTS_STATUS_TABS,
|
||||
|
@ -26,8 +25,6 @@ import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert
|
|||
import mockAlerts from '../mocks/alerts.json';
|
||||
import Tracking from '~/tracking';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
jest.mock('~/lib/utils/url_utility', () => ({
|
||||
visitUrl: jest.fn().mockName('visitUrlMock'),
|
||||
joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
|
||||
|
@ -391,14 +388,15 @@ describe('AlertManagementList', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls `createFlash` when request fails', () => {
|
||||
it('shows an error when request fails', () => {
|
||||
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error()));
|
||||
findFirstStatusOption().vm.$emit('click');
|
||||
wrapper.setData({
|
||||
errored: true,
|
||||
});
|
||||
|
||||
setImmediate(() => {
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
'There was an error while updating the status of the alert. Please try again.',
|
||||
);
|
||||
wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.find('[data-testid="alert-error"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { trackAlertStatusUpdateOptions } from '~/alert_management/constants';
|
||||
import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue';
|
||||
|
@ -14,7 +14,7 @@ describe('Alert Details Sidebar Status', () => {
|
|||
const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
||||
|
||||
function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) {
|
||||
wrapper = shallowMount(AlertSidebarStatus, {
|
||||
wrapper = mount(AlertSidebarStatus, {
|
||||
propsData: {
|
||||
alert: { ...mockAlert },
|
||||
...data,
|
||||
|
|
|
@ -110,7 +110,7 @@ describe('DiffGutterAvatars', () => {
|
|||
it('returns truncated version of comment if it is longer than max length', () => {
|
||||
const note = wrapper.vm.discussions[0].notes[1];
|
||||
|
||||
expect(wrapper.vm.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...');
|
||||
expect(wrapper.vm.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is rea…');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
import createEventHub from '~/helpers/event_hub_factory';
|
||||
|
||||
const TEST_EVENT = 'foobar';
|
||||
const TEST_EVENT_2 = 'testevent';
|
||||
|
||||
describe('event bus factory', () => {
|
||||
let eventBus;
|
||||
let handler;
|
||||
let otherHandlers;
|
||||
|
||||
beforeEach(() => {
|
||||
eventBus = createEventHub();
|
||||
handler = jest.fn();
|
||||
otherHandlers = [jest.fn(), jest.fn()];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
eventBus.dispose();
|
||||
eventBus = null;
|
||||
});
|
||||
|
||||
|
@ -48,22 +52,6 @@ describe('event bus factory', () => {
|
|||
|
||||
expect(handler).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('does not call handler after $off with handler', () => {
|
||||
eventBus.$off(TEST_EVENT, handler);
|
||||
|
||||
eventBus.$emit(TEST_EVENT);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not call handler after $off', () => {
|
||||
eventBus.$off(TEST_EVENT);
|
||||
|
||||
eventBus.$emit(TEST_EVENT);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('$once', () => {
|
||||
|
@ -102,4 +90,55 @@ describe('event bus factory', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('$off', () => {
|
||||
beforeEach(() => {
|
||||
otherHandlers.forEach(x => eventBus.$on(TEST_EVENT, x));
|
||||
eventBus.$on(TEST_EVENT, handler);
|
||||
});
|
||||
|
||||
it('can be called on event with no handlers', () => {
|
||||
expect(() => {
|
||||
eventBus.$off(TEST_EVENT_2);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('can be called on event with no handlers, with a handler', () => {
|
||||
expect(() => {
|
||||
eventBus.$off(TEST_EVENT_2, handler);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('with a handler, will no longer call that handler', () => {
|
||||
eventBus.$off(TEST_EVENT, handler);
|
||||
|
||||
eventBus.$emit(TEST_EVENT);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 1));
|
||||
});
|
||||
|
||||
it('without a handler, will no longer call any handlers', () => {
|
||||
eventBus.$off(TEST_EVENT);
|
||||
|
||||
eventBus.$emit(TEST_EVENT);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 0));
|
||||
});
|
||||
});
|
||||
|
||||
describe('$emit', () => {
|
||||
beforeEach(() => {
|
||||
otherHandlers.forEach(x => eventBus.$on(TEST_EVENT_2, x));
|
||||
eventBus.$on(TEST_EVENT, handler);
|
||||
});
|
||||
|
||||
it('only calls handlers for given type', () => {
|
||||
eventBus.$emit(TEST_EVENT, 'arg1');
|
||||
|
||||
expect(handler).toHaveBeenCalledWith('arg1');
|
||||
expect(otherHandlers.map(x => x.mock.calls.length)).toEqual(otherHandlers.map(() => 0));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -145,6 +145,56 @@ describe('text_utility', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('truncate', () => {
|
||||
it('returns the original string when str length is less than maxLength', () => {
|
||||
const str = 'less than 20 chars';
|
||||
expect(textUtils.truncate(str, 20)).toEqual(str);
|
||||
});
|
||||
|
||||
it('returns truncated string when str length is more than maxLength', () => {
|
||||
const str = 'more than 10 chars';
|
||||
expect(textUtils.truncate(str, 10)).toEqual(`${str.substring(0, 10 - 1)}…`);
|
||||
});
|
||||
|
||||
it('returns the original string when rendered width is exactly equal to maxWidth', () => {
|
||||
const str = 'Exactly 16 chars';
|
||||
expect(textUtils.truncate(str, 16)).toEqual(str);
|
||||
});
|
||||
});
|
||||
|
||||
describe('truncateWidth', () => {
|
||||
const clientWidthDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'clientWidth');
|
||||
|
||||
beforeAll(() => {
|
||||
// Mock measured width of ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
Object.defineProperty(Element.prototype, 'clientWidth', {
|
||||
value: 431,
|
||||
writable: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
Object.defineProperty(Element.prototype, 'clientWidth', clientWidthDescriptor);
|
||||
});
|
||||
|
||||
it('returns the original string when rendered width is less than maxWidth', () => {
|
||||
const str = '< 80px';
|
||||
expect(textUtils.truncateWidth(str)).toEqual(str);
|
||||
});
|
||||
|
||||
it('returns truncated string when rendered width is more than maxWidth', () => {
|
||||
const str = 'This is wider than 80px';
|
||||
expect(textUtils.truncateWidth(str)).toEqual(`${str.substring(0, 10)}…`);
|
||||
});
|
||||
|
||||
it('returns the original string when rendered width is exactly equal to maxWidth', () => {
|
||||
const str = 'Exactly 159.62962962962965px';
|
||||
expect(textUtils.truncateWidth(str, { maxWidth: 159.62962962962965, fontSize: 10 })).toEqual(
|
||||
str,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('truncateSha', () => {
|
||||
it('shortens SHAs to 8 characters', () => {
|
||||
expect(textUtils.truncateSha('verylongsha')).toBe('verylong');
|
||||
|
|
|
@ -4,6 +4,7 @@ import ReleaseBlockAssets from '~/releases/components/release_block_assets.vue';
|
|||
import { ASSET_LINK_TYPE } from '~/releases/constants';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import { assets } from '../mock_data';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
describe('Release block assets', () => {
|
||||
let wrapper;
|
||||
|
@ -30,7 +31,7 @@ describe('Release block assets', () => {
|
|||
wrapper.findAll('h5').filter(h5 => h5.text() === sections[type]);
|
||||
|
||||
beforeEach(() => {
|
||||
defaultProps = { assets };
|
||||
defaultProps = { assets: cloneDeep(assets) };
|
||||
});
|
||||
|
||||
describe('with default props', () => {
|
||||
|
@ -96,6 +97,35 @@ describe('Release block assets', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('sources', () => {
|
||||
const testSources = ({ shouldSourcesBeRendered }) => {
|
||||
assets.sources.forEach(s => {
|
||||
expect(wrapper.find(`a[href="${s.url}"]`).exists()).toBe(shouldSourcesBeRendered);
|
||||
});
|
||||
};
|
||||
|
||||
describe('when the release has sources', () => {
|
||||
beforeEach(() => {
|
||||
createComponent(defaultProps);
|
||||
});
|
||||
|
||||
it('renders sources', () => {
|
||||
testSources({ shouldSourcesBeRendered: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the release does not have sources', () => {
|
||||
beforeEach(() => {
|
||||
delete defaultProps.assets.sources;
|
||||
createComponent(defaultProps);
|
||||
});
|
||||
|
||||
it('does not render any sources', () => {
|
||||
testSources({ shouldSourcesBeRendered: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('external vs internal links', () => {
|
||||
const containsExternalSourceIndicator = () =>
|
||||
wrapper.contains('[data-testid="external-link-indicator"]');
|
||||
|
|
|
@ -2,7 +2,7 @@ import {
|
|||
buildUneditableOpenTokens,
|
||||
buildUneditableCloseToken,
|
||||
buildUneditableTokens,
|
||||
} from '~/vue_shared/components/rich_content_editor/services/renderers//build_uneditable_token';
|
||||
} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
|
||||
|
||||
import {
|
||||
originToken,
|
||||
|
|
|
@ -2,7 +2,7 @@ import renderer from '~/vue_shared/components/rich_content_editor/services/rende
|
|||
import {
|
||||
buildUneditableOpenTokens,
|
||||
buildUneditableCloseToken,
|
||||
} from '~/vue_shared/components/rich_content_editor/services/renderers//build_uneditable_token';
|
||||
} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
|
||||
|
||||
import { kramdownListNode, normalListNode } from '../../mock_data';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_kramdown_text';
|
||||
import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers//build_uneditable_token';
|
||||
import { buildUneditableTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
|
||||
|
||||
import { kramdownTextNode, normalTextNode } from '../../mock_data';
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe GitlabSchema.types['MilestoneStats'] do
|
||||
it { expect(described_class).to require_graphql_authorizations(:read_milestone) }
|
||||
|
||||
it 'has the expected fields' do
|
||||
expected_fields = %w[
|
||||
total_issues_count closed_issues_count
|
||||
]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
end
|
||||
end
|
|
@ -6,4 +6,21 @@ RSpec.describe GitlabSchema.types['Milestone'] do
|
|||
specify { expect(described_class.graphql_name).to eq('Milestone') }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:read_milestone) }
|
||||
|
||||
it 'has the expected fields' do
|
||||
expected_fields = %w[
|
||||
id title description state web_path
|
||||
due_date start_date created_at updated_at
|
||||
project_milestone group_milestone subgroup_milestone
|
||||
stats
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
||||
describe 'stats field' do
|
||||
subject { described_class.fields['stats'] }
|
||||
|
||||
it { is_expected.to have_graphql_type(Types::MilestoneStatsType) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,13 +13,15 @@ describe Gitlab::Ci::Build::Releaser do
|
|||
name: 'Release $CI_COMMIT_SHA',
|
||||
description: 'Created using the release-cli $EXTRA_DESCRIPTION',
|
||||
tag_name: 'release-$CI_COMMIT_SHA',
|
||||
ref: '$CI_COMMIT_SHA'
|
||||
ref: '$CI_COMMIT_SHA',
|
||||
milestones: %w[m1 m2 m3],
|
||||
released_at: '2020-07-15T08:00:00Z'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'generates the script' do
|
||||
expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA"'])
|
||||
expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA" --released-at "2020-07-15T08:00:00Z" --milestone "m1" --milestone "m2" --milestone "m3"'])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -27,10 +29,12 @@ describe Gitlab::Ci::Build::Releaser do
|
|||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:node_name, :node_value, :result) do
|
||||
'name' | 'Release $CI_COMMIT_SHA' | 'release-cli create --name "Release $CI_COMMIT_SHA"'
|
||||
'description' | 'Release-cli $EXTRA_DESCRIPTION' | 'release-cli create --description "Release-cli $EXTRA_DESCRIPTION"'
|
||||
'tag_name' | 'release-$CI_COMMIT_SHA' | 'release-cli create --tag-name "release-$CI_COMMIT_SHA"'
|
||||
'ref' | '$CI_COMMIT_SHA' | 'release-cli create --ref "$CI_COMMIT_SHA"'
|
||||
:name | 'Release $CI_COMMIT_SHA' | 'release-cli create --name "Release $CI_COMMIT_SHA"'
|
||||
:description | 'Release-cli $EXTRA_DESCRIPTION' | 'release-cli create --description "Release-cli $EXTRA_DESCRIPTION"'
|
||||
:tag_name | 'release-$CI_COMMIT_SHA' | 'release-cli create --tag-name "release-$CI_COMMIT_SHA"'
|
||||
:ref | '$CI_COMMIT_SHA' | 'release-cli create --ref "$CI_COMMIT_SHA"'
|
||||
:milestones | %w[m1 m2 m3] | 'release-cli create --milestone "m1" --milestone "m2" --milestone "m3"'
|
||||
:released_at | '2020-07-15T08:00:00Z' | 'release-cli create --released-at "2020-07-15T08:00:00Z"'
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
|
@ -5,21 +5,32 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::Ci::Config::Entry::Release do
|
||||
let(:entry) { described_class.new(config) }
|
||||
|
||||
shared_examples_for 'a valid entry' do
|
||||
describe '#value' do
|
||||
it 'returns release configuration' do
|
||||
expect(entry.value).to eq config
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it 'is valid' do
|
||||
expect(entry).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'reports error' do |message|
|
||||
it 'reports error' do
|
||||
expect(entry.errors)
|
||||
.to include message
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validation' do
|
||||
context 'when entry config value is correct' do
|
||||
let(:config) { { tag_name: 'v0.06', description: "./release_changelog.txt" } }
|
||||
|
||||
describe '#value' do
|
||||
it 'returns release configuration' do
|
||||
expect(entry.value).to eq config
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it 'is valid' do
|
||||
expect(entry).to be_valid
|
||||
end
|
||||
end
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context "when value includes 'assets' keyword" do
|
||||
|
@ -36,17 +47,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do
|
|||
}
|
||||
end
|
||||
|
||||
describe '#value' do
|
||||
it 'returns release configuration' do
|
||||
expect(entry.value).to eq config
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it 'is valid' do
|
||||
expect(entry).to be_valid
|
||||
end
|
||||
end
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context "when value includes 'name' keyword" do
|
||||
|
@ -58,16 +59,104 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do
|
|||
}
|
||||
end
|
||||
|
||||
describe '#value' do
|
||||
it 'returns release configuration' do
|
||||
expect(entry.value).to eq config
|
||||
end
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context "when value includes 'ref' keyword" do
|
||||
let(:config) do
|
||||
{
|
||||
tag_name: 'v0.06',
|
||||
description: "./release_changelog.txt",
|
||||
name: "Release $CI_TAG_NAME",
|
||||
ref: 'b3235930aa443112e639f941c69c578912189bdd'
|
||||
}
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it 'is valid' do
|
||||
expect(entry).to be_valid
|
||||
end
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context "when value includes 'released_at' keyword" do
|
||||
let(:config) do
|
||||
{
|
||||
tag_name: 'v0.06',
|
||||
description: "./release_changelog.txt",
|
||||
name: "Release $CI_TAG_NAME",
|
||||
released_at: '2019-03-15T08:00:00Z'
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context "when value includes 'milestones' keyword" do
|
||||
let(:config) do
|
||||
{
|
||||
tag_name: 'v0.06',
|
||||
description: "./release_changelog.txt",
|
||||
name: "Release $CI_TAG_NAME",
|
||||
milestones: milestones
|
||||
}
|
||||
end
|
||||
|
||||
context 'for an array of milestones' do
|
||||
let(:milestones) { %w[m1 m2 m3] }
|
||||
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context 'for a single milestone' do
|
||||
let(:milestones) { 'm1' }
|
||||
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
end
|
||||
|
||||
context "when value includes 'ref' keyword" do
|
||||
let(:config) do
|
||||
{
|
||||
tag_name: 'v0.06',
|
||||
description: "./release_changelog.txt",
|
||||
name: "Release $CI_TAG_NAME",
|
||||
ref: 'b3235930aa443112e639f941c69c578912189bdd'
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context "when value includes 'released_at' keyword" do
|
||||
let(:config) do
|
||||
{
|
||||
tag_name: 'v0.06',
|
||||
description: "./release_changelog.txt",
|
||||
name: "Release $CI_TAG_NAME",
|
||||
released_at: '2019-03-15T08:00:00Z'
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context "when value includes 'milestones' keyword" do
|
||||
let(:config) do
|
||||
{
|
||||
tag_name: 'v0.06',
|
||||
description: "./release_changelog.txt",
|
||||
name: "Release $CI_TAG_NAME",
|
||||
milestones: milestones
|
||||
}
|
||||
end
|
||||
|
||||
context 'for an array of milestones' do
|
||||
let(:milestones) { %w[m1 m2 m3] }
|
||||
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
|
||||
context 'for a single milestone' do
|
||||
let(:milestones) { 'm1' }
|
||||
|
||||
it_behaves_like 'a valid entry'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -76,37 +165,61 @@ RSpec.describe Gitlab::Ci::Config::Entry::Release do
|
|||
context 'when value of attribute is invalid' do
|
||||
let(:config) { { description: 10 } }
|
||||
|
||||
it 'reports error' do
|
||||
expect(entry.errors)
|
||||
.to include 'release description should be a string'
|
||||
end
|
||||
it_behaves_like 'reports error', 'release description should be a string'
|
||||
end
|
||||
|
||||
context 'when release description is missing' do
|
||||
let(:config) { { tag_name: 'v0.06' } }
|
||||
|
||||
it 'reports error' do
|
||||
expect(entry.errors)
|
||||
.to include "release description can't be blank"
|
||||
end
|
||||
it_behaves_like 'reports error', "release description can't be blank"
|
||||
end
|
||||
|
||||
context 'when release tag_name is missing' do
|
||||
let(:config) { { description: "./release_changelog.txt" } }
|
||||
|
||||
it 'reports error' do
|
||||
expect(entry.errors)
|
||||
.to include "release tag name can't be blank"
|
||||
end
|
||||
it_behaves_like 'reports error', "release tag name can't be blank"
|
||||
end
|
||||
|
||||
context 'when there is an unknown key present' do
|
||||
let(:config) { { test: 100 } }
|
||||
|
||||
it 'reports error' do
|
||||
expect(entry.errors)
|
||||
.to include 'release config contains unknown keys: test'
|
||||
end
|
||||
it_behaves_like 'reports error', 'release config contains unknown keys: test'
|
||||
end
|
||||
|
||||
context 'when `released_at` is not a valid date' do
|
||||
let(:config) { { released_at: 'ABC123' } }
|
||||
|
||||
it_behaves_like 'reports error', 'release released at must be a valid datetime'
|
||||
end
|
||||
|
||||
context 'when `ref` is not valid' do
|
||||
let(:config) { { ref: 'ABC123' } }
|
||||
|
||||
it_behaves_like 'reports error', 'release ref must be a valid ref'
|
||||
end
|
||||
|
||||
context 'when `milestones` is not an array of strings' do
|
||||
let(:config) { { milestones: [1, 2, 3] } }
|
||||
|
||||
it_behaves_like 'reports error', 'release milestones should be an array of strings or a string'
|
||||
end
|
||||
|
||||
context 'when `released_at` is not a valid date' do
|
||||
let(:config) { { released_at: 'ABC123' } }
|
||||
|
||||
it_behaves_like 'reports error', 'release released at must be a valid datetime'
|
||||
end
|
||||
|
||||
context 'when `ref` is not valid' do
|
||||
let(:config) { { ref: 'ABC123' } }
|
||||
|
||||
it_behaves_like 'reports error', 'release ref must be a valid ref'
|
||||
end
|
||||
|
||||
context 'when `milestones` is not an array of strings' do
|
||||
let(:config) { { milestones: [1, 2, 3] } }
|
||||
|
||||
it_behaves_like 'reports error', 'release milestones should be an array of strings or a string'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1397,6 +1397,9 @@ module Gitlab
|
|||
tag_name: "$CI_COMMIT_TAG",
|
||||
name: "Release $CI_TAG_NAME",
|
||||
description: "./release_changelog.txt",
|
||||
ref: 'b3235930aa443112e639f941c69c578912189bdd',
|
||||
released_at: '2019-03-15T08:00:00Z',
|
||||
milestones: %w[m1 m2 m3],
|
||||
assets: {
|
||||
links: [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Diff::StatsCache, :use_clean_rails_memory_store_caching do
|
||||
subject(:stats_cache) { described_class.new(cachable_key: cachable_key) }
|
||||
|
||||
let(:key) { ['diff_stats', cachable_key, described_class::VERSION].join(":") }
|
||||
let(:cachable_key) { 'cachecachecache' }
|
||||
let(:stat) { Gitaly::DiffStats.new(path: 'temp', additions: 10, deletions: 15) }
|
||||
let(:stats) { Gitlab::Git::DiffStatsCollection.new([stat]) }
|
||||
let(:cache) { Rails.cache }
|
||||
|
||||
describe '#read' do
|
||||
before do
|
||||
stats_cache.write_if_empty(stats)
|
||||
end
|
||||
|
||||
it 'returns the expected stats' do
|
||||
expect(stats_cache.read.to_json).to eq(stats.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#write_if_empty' do
|
||||
context 'when the cache already exists' do
|
||||
before do
|
||||
Rails.cache.write(key, true)
|
||||
end
|
||||
|
||||
it 'does not write the stats' do
|
||||
expect(cache).not_to receive(:write)
|
||||
|
||||
stats_cache.write_if_empty(stats)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the cache does not exist' do
|
||||
it 'writes the stats' do
|
||||
expect(cache)
|
||||
.to receive(:write)
|
||||
.with(key, stats.as_json, expires_in: described_class::EXPIRATION)
|
||||
.and_call_original
|
||||
|
||||
stats_cache.write_if_empty(stats)
|
||||
|
||||
expect(stats_cache.read.to_a).to eq(stats.to_a)
|
||||
end
|
||||
|
||||
context 'when given non utf-8 characters' do
|
||||
let(:non_utf8_path) { '你好'.b }
|
||||
let(:stat) { Gitaly::DiffStats.new(path: non_utf8_path, additions: 10, deletions: 15) }
|
||||
|
||||
it 'writes the stats' do
|
||||
expect(cache)
|
||||
.to receive(:write)
|
||||
.with(key, stats.as_json, expires_in: described_class::EXPIRATION)
|
||||
.and_call_original
|
||||
|
||||
stats_cache.write_if_empty(stats)
|
||||
|
||||
expect(stats_cache.read.to_a).to eq(stats.to_a)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given empty stats' do
|
||||
let(:stats) { nil }
|
||||
|
||||
it 'does not write the stats' do
|
||||
expect(cache).not_to receive(:write)
|
||||
|
||||
stats_cache.write_if_empty(stats)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clear' do
|
||||
it 'clears cache' do
|
||||
expect(cache).to receive(:delete).with(key)
|
||||
|
||||
stats_cache.clear
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,11 +4,11 @@ require "spec_helper"
|
|||
|
||||
describe Gitlab::Git::DiffStatsCollection do
|
||||
let(:stats_a) do
|
||||
double(Gitaly::DiffStats, additions: 10, deletions: 15, path: 'foo')
|
||||
Gitaly::DiffStats.new(additions: 10, deletions: 15, path: 'foo')
|
||||
end
|
||||
|
||||
let(:stats_b) do
|
||||
double(Gitaly::DiffStats, additions: 5, deletions: 1, path: 'bar')
|
||||
Gitaly::DiffStats.new(additions: 5, deletions: 1, path: 'bar')
|
||||
end
|
||||
|
||||
let(:diff_stats) { [stats_a, stats_b] }
|
||||
|
|
|
@ -7,16 +7,17 @@ RSpec.describe 'Milestones through GroupQuery' do
|
|||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:now) { Time.now }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:milestone_1) { create(:milestone, group: group) }
|
||||
let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
|
||||
let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
|
||||
let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
|
||||
let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
|
||||
|
||||
let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
|
||||
|
||||
describe 'Get list of milestones from a group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:milestone_1) { create(:milestone, group: group) }
|
||||
let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
|
||||
let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
|
||||
let_it_be(:milestone_4) { create(:milestone, group: group, state: :closed, start_date: now - 2.days, due_date: now - 1.day) }
|
||||
let_it_be(:milestone_from_other_group) { create(:milestone, group: create(:group)) }
|
||||
|
||||
let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
|
||||
|
||||
context 'when the request is correct' do
|
||||
before do
|
||||
fetch_milestones(user)
|
||||
|
@ -120,4 +121,89 @@ RSpec.describe 'Milestones through GroupQuery' do
|
|||
node_array(milestone_data, extract_attribute)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'ensures each field returns the correct value' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:milestone) { create(:milestone, group: group, start_date: now, due_date: now + 1.day) }
|
||||
let_it_be(:open_issue) { create(:issue, project: project, milestone: milestone) }
|
||||
let_it_be(:closed_issue) { create(:issue, :closed, project: project, milestone: milestone) }
|
||||
|
||||
let(:milestone_query) do
|
||||
%{
|
||||
id
|
||||
title
|
||||
description
|
||||
state
|
||||
webPath
|
||||
dueDate
|
||||
startDate
|
||||
createdAt
|
||||
updatedAt
|
||||
projectMilestone
|
||||
groupMilestone
|
||||
subgroupMilestone
|
||||
}
|
||||
end
|
||||
|
||||
def post_query
|
||||
full_query = graphql_query_for("group",
|
||||
{ full_path: group.full_path },
|
||||
[query_graphql_field("milestones", nil, "nodes { #{milestone_query} }")]
|
||||
)
|
||||
|
||||
post_graphql(full_query, current_user: user)
|
||||
|
||||
graphql_data.dig('group', 'milestones', 'nodes', 0)
|
||||
end
|
||||
|
||||
it 'returns correct values for scalar fields' do
|
||||
expect(post_query).to eq({
|
||||
'id' => global_id_of(milestone),
|
||||
'title' => milestone.title,
|
||||
'description' => milestone.description,
|
||||
'state' => 'active',
|
||||
'webPath' => milestone_path(milestone),
|
||||
'dueDate' => milestone.due_date.iso8601,
|
||||
'startDate' => milestone.start_date.iso8601,
|
||||
'createdAt' => milestone.created_at.iso8601,
|
||||
'updatedAt' => milestone.updated_at.iso8601,
|
||||
'projectMilestone' => false,
|
||||
'groupMilestone' => true,
|
||||
'subgroupMilestone' => false
|
||||
})
|
||||
end
|
||||
|
||||
context 'milestone statistics' do
|
||||
let(:milestone_query) do
|
||||
%{
|
||||
stats {
|
||||
totalIssuesCount
|
||||
closedIssuesCount
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns the correct milestone statistics' do
|
||||
expect(post_query).to eq({
|
||||
'stats' => {
|
||||
'totalIssuesCount' => 2,
|
||||
'closedIssuesCount' => 1
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
context 'when the graphql_milestone_stats feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(graphql_milestone_stats: false)
|
||||
end
|
||||
|
||||
it 'returns nil for the stats field' do
|
||||
expect(post_query).to eq({
|
||||
'stats' => nil
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -668,7 +668,7 @@ RSpec.describe API::Runner, :clean_gitlab_redis_shared_state do
|
|||
{
|
||||
"name" => "release",
|
||||
"script" =>
|
||||
["release-cli create --ref \"$CI_COMMIT_SHA\" --name \"Release $CI_COMMIT_SHA\" --tag-name \"release-$CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\""],
|
||||
["release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\""],
|
||||
"timeout" => 3600,
|
||||
"when" => "on_success",
|
||||
"allow_failure" => false
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe DesignManagement::GenerateImageVersionsService do
|
||||
RSpec.describe DesignManagement::GenerateImageVersionsService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:version) { create(:design, :with_lfs_file, issue: issue).versions.first }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe DesignManagement::SaveDesignsService do
|
||||
RSpec.describe DesignManagement::SaveDesignsService do
|
||||
include DesignManagementTestHelpers
|
||||
include ConcurrentHelpers
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Discussions::CaptureDiffNotePositionService do
|
||||
RSpec.describe Discussions::CaptureDiffNotePositionService do
|
||||
subject { described_class.new(note.noteable, paths) }
|
||||
|
||||
context 'image note on diff' do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Discussions::CaptureDiffNotePositionsService do
|
||||
RSpec.describe Discussions::CaptureDiffNotePositionsService do
|
||||
context 'when merge request has a discussion' do
|
||||
let(:source_branch) { 'compare-with-merge-head-source' }
|
||||
let(:target_branch) { 'compare-with-merge-head-target' }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Discussions::ResolveService do
|
||||
RSpec.describe Discussions::ResolveService do
|
||||
describe '#execute' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { create(:user, developer_projects: [project]) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Discussions::UpdateDiffPositionService do
|
||||
RSpec.describe Discussions::UpdateDiffPositionService do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:current_user) { project.owner }
|
||||
let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe DraftNotes::CreateService do
|
||||
RSpec.describe DraftNotes::CreateService do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:project) { merge_request.target_project }
|
||||
let(:user) { merge_request.author }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe DraftNotes::DestroyService do
|
||||
RSpec.describe DraftNotes::DestroyService do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:project) { merge_request.target_project }
|
||||
let(:user) { merge_request.author }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe DraftNotes::PublishService do
|
||||
RSpec.describe DraftNotes::PublishService do
|
||||
include RepoHelpers
|
||||
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Emails::ConfirmService do
|
||||
RSpec.describe Emails::ConfirmService do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
subject(:service) { described_class.new(user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Emails::CreateService do
|
||||
RSpec.describe Emails::CreateService do
|
||||
let(:user) { create(:user) }
|
||||
let(:opts) { { email: 'new@email.com', user: user } }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Emails::DestroyService do
|
||||
RSpec.describe Emails::DestroyService do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:email) { create(:email, user: user) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Environments::AutoStopService, :clean_gitlab_redis_shared_state do
|
||||
RSpec.describe Environments::AutoStopService, :clean_gitlab_redis_shared_state do
|
||||
include CreateEnvironmentsHelpers
|
||||
include ExclusiveLeaseHelpers
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Environments::ResetAutoStopService do
|
||||
RSpec.describe Environments::ResetAutoStopService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
|
||||
let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ErrorTracking::BaseService do
|
||||
RSpec.describe ErrorTracking::BaseService do
|
||||
describe '#compose_response' do
|
||||
let(:project) { double('project') }
|
||||
let(:user) { double('user') }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ErrorTracking::IssueDetailsService do
|
||||
RSpec.describe ErrorTracking::IssueDetailsService do
|
||||
include_context 'sentry error tracking context'
|
||||
|
||||
subject { described_class.new(project, user, params) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ErrorTracking::IssueLatestEventService do
|
||||
RSpec.describe ErrorTracking::IssueLatestEventService do
|
||||
include_context 'sentry error tracking context'
|
||||
|
||||
subject { described_class.new(project, user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ErrorTracking::IssueUpdateService do
|
||||
RSpec.describe ErrorTracking::IssueUpdateService do
|
||||
include_context 'sentry error tracking context'
|
||||
|
||||
let(:arguments) { { issue_id: non_existing_record_id, status: 'resolved' } }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ErrorTracking::ListIssuesService do
|
||||
RSpec.describe ErrorTracking::ListIssuesService do
|
||||
include_context 'sentry error tracking context'
|
||||
|
||||
let(:params) { { search_term: 'something', sort: 'last_seen', cursor: 'some-cursor' } }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ErrorTracking::ListProjectsService do
|
||||
RSpec.describe ErrorTracking::ListProjectsService do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe EventCreateService do
|
||||
RSpec.describe EventCreateService do
|
||||
let(:service) { described_class.new }
|
||||
|
||||
let_it_be(:user, reload: true) { create :user }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Events::RenderService do
|
||||
RSpec.describe Events::RenderService do
|
||||
describe '#execute' do
|
||||
let!(:note) { build(:note) }
|
||||
let!(:event) { build(:event, target: note, project: note.project) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require "spec_helper"
|
||||
|
||||
describe Files::CreateService do
|
||||
RSpec.describe Files::CreateService do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:repository) { project.repository }
|
||||
let(:user) { create(:user, :commit_email) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require "spec_helper"
|
||||
|
||||
describe Files::DeleteService do
|
||||
RSpec.describe Files::DeleteService do
|
||||
subject { described_class.new(project, user, commit_params) }
|
||||
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require "spec_helper"
|
||||
|
||||
describe Files::MultiService do
|
||||
RSpec.describe Files::MultiService do
|
||||
subject { described_class.new(project, user, commit_params) }
|
||||
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require "spec_helper"
|
||||
|
||||
describe Files::UpdateService do
|
||||
RSpec.describe Files::UpdateService do
|
||||
subject { described_class.new(project, user, commit_params) }
|
||||
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Git::BaseHooksService do
|
||||
RSpec.describe Git::BaseHooksService do
|
||||
include RepoHelpers
|
||||
include GitHelpers
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Git::BranchHooksService do
|
||||
RSpec.describe Git::BranchHooksService do
|
||||
include RepoHelpers
|
||||
include ProjectForksHelper
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Git::BranchPushService, services: true do
|
||||
RSpec.describe Git::BranchPushService, services: true do
|
||||
include RepoHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Git::ProcessRefChangesService do
|
||||
RSpec.describe Git::ProcessRefChangesService do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { project.owner }
|
||||
let(:params) { { changes: git_changes } }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Git::TagHooksService, :service do
|
||||
RSpec.describe Git::TagHooksService, :service do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Git::TagPushService do
|
||||
RSpec.describe Git::TagPushService do
|
||||
include RepoHelpers
|
||||
include GitHelpers
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Git::WikiPushService::Change do
|
||||
RSpec.describe Git::WikiPushService::Change do
|
||||
subject { described_class.new(project_wiki, change, raw_change) }
|
||||
|
||||
let(:project_wiki) { double('ProjectWiki') }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Git::WikiPushService, services: true do
|
||||
RSpec.describe Git::WikiPushService, services: true do
|
||||
include RepoHelpers
|
||||
|
||||
let_it_be(:key_id) { create(:key, user: current_user).shell_id }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe GpgKeys::CreateService do
|
||||
RSpec.describe GpgKeys::CreateService do
|
||||
let(:user) { create(:user) }
|
||||
let(:params) { attributes_for(:gpg_key) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Grafana::ProxyService do
|
||||
RSpec.describe Grafana::ProxyService do
|
||||
include ReactiveCachingHelpers
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe GravatarService do
|
||||
RSpec.describe GravatarService do
|
||||
describe '#execute' do
|
||||
let(:url) { 'http://example.com/avatar?hash=%{hash}&size=%{size}&email=%{email}&username=%{username}' }
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::AutoDevopsService, '#execute' do
|
||||
RSpec.describe Groups::AutoDevopsService, '#execute' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:group_params) { { auto_devops_enabled: '0' } }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::CreateService, '#execute' do
|
||||
RSpec.describe Groups::CreateService, '#execute' do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::DeployTokens::CreateService do
|
||||
RSpec.describe Groups::DeployTokens::CreateService do
|
||||
it_behaves_like 'a deploy token creation service' do
|
||||
let(:entity) { create(:group) }
|
||||
let(:deploy_token_class) { GroupDeployToken }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::DeployTokens::DestroyService do
|
||||
RSpec.describe Groups::DeployTokens::DestroyService do
|
||||
it_behaves_like 'a deploy token deletion service' do
|
||||
let_it_be(:entity) { create(:group) }
|
||||
let_it_be(:deploy_token_class) { GroupDeployToken }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::DestroyService do
|
||||
RSpec.describe Groups::DestroyService do
|
||||
include DatabaseConnectionHelpers
|
||||
|
||||
let!(:user) { create(:user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::GroupLinks::CreateService, '#execute' do
|
||||
RSpec.describe Groups::GroupLinks::CreateService, '#execute' do
|
||||
let(:parent_group_user) { create(:user) }
|
||||
let(:group_user) { create(:user) }
|
||||
let(:child_group_user) { create(:user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::GroupLinks::DestroyService, '#execute' do
|
||||
RSpec.describe Groups::GroupLinks::DestroyService, '#execute' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
let_it_be(:group) { create(:group, :private) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::GroupLinks::UpdateService, '#execute' do
|
||||
RSpec.describe Groups::GroupLinks::UpdateService, '#execute' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
let_it_be(:group) { create(:group, :private) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::ImportExport::ExportService do
|
||||
RSpec.describe Groups::ImportExport::ExportService do
|
||||
describe '#async_execute' do
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::ImportExport::ImportService do
|
||||
RSpec.describe Groups::ImportExport::ImportService do
|
||||
describe '#async_execute' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::NestedCreateService do
|
||||
RSpec.describe Groups::NestedCreateService do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
subject(:service) { described_class.new(user, params) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Groups::TransferService do
|
||||
RSpec.describe Groups::TransferService do
|
||||
let(:user) { create(:user) }
|
||||
let(:new_parent_group) { create(:group, :public) }
|
||||
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue