Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a2f16969fa
commit
f9fe7cda4b
|
|
@ -1 +1 @@
|
|||
add5f3dd182c99b4d9e1cf93e45fec1214c00659
|
||||
9fd57cbd0b63d448f9a9555b53f065ee1c110199
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees
|
|||
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
|
||||
import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
|
||||
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/getIssueParticipants.query.graphql';
|
||||
import searchUsers from '~/boards/queries/users_search.query.graphql';
|
||||
import searchUsers from '~/boards/graphql/users_search.query.graphql';
|
||||
|
||||
export default {
|
||||
noSearchDelay: 0,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import {
|
|||
import httpStatusCodes from '~/lib/utils/http_status';
|
||||
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import projectQuery from '../queries/project_boards.query.graphql';
|
||||
import groupQuery from '../queries/group_boards.query.graphql';
|
||||
import projectQuery from '../graphql/project_boards.query.graphql';
|
||||
import groupQuery from '../graphql/group_boards.query.graphql';
|
||||
|
||||
import boardsStore from '../stores/boards_store';
|
||||
import BoardForm from './board_form.vue';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
|
||||
import groupMilestones from '../../queries/group_milestones.query.graphql';
|
||||
import groupMilestones from '../../graphql/group_milestones.query.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import { __, s__ } from '~/locale';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
|
||||
#import "ee_else_ce/boards/graphql/board_list.fragment.graphql"
|
||||
|
||||
mutation CreateBoardList(
|
||||
$boardId: BoardID!
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
|
||||
#import "ee_else_ce/boards/graphql/board_list.fragment.graphql"
|
||||
|
||||
query ListIssues(
|
||||
$fullPath: ID!
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#import "ee_else_ce/boards/queries/board.fragment.graphql"
|
||||
#import "ee_else_ce/boards/graphql/board.fragment.graphql"
|
||||
|
||||
query group_boards($fullPath: ID!) {
|
||||
group(fullPath: $fullPath) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#import "ee_else_ce/boards/queries/issue.fragment.graphql"
|
||||
#import "ee_else_ce/boards/graphql/issue.fragment.graphql"
|
||||
|
||||
mutation CreateIssue($input: CreateIssueInput!) {
|
||||
createIssue(input: $input) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#import "ee_else_ce/boards/queries/issue.fragment.graphql"
|
||||
#import "ee_else_ce/boards/graphql/issue.fragment.graphql"
|
||||
|
||||
mutation IssueMoveList(
|
||||
$projectPath: ID!
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#import "ee_else_ce/boards/queries/issue.fragment.graphql"
|
||||
#import "ee_else_ce/boards/graphql/issue.fragment.graphql"
|
||||
|
||||
query ListIssues(
|
||||
$fullPath: ID!
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#import "ee_else_ce/boards/queries/board.fragment.graphql"
|
||||
#import "ee_else_ce/boards/graphql/board.fragment.graphql"
|
||||
|
||||
query project_boards($fullPath: ID!) {
|
||||
project(fullPath: $fullPath) {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { pick } from 'lodash';
|
||||
|
||||
import boardListsQuery from 'ee_else_ce/boards/queries/board_lists.query.graphql';
|
||||
import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
|
||||
import createGqClient, { fetchPolicies } from '~/lib/graphql';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { BoardType, ListType, inactiveId, DEFAULT_LABELS } from '~/boards/constants';
|
||||
|
|
@ -14,18 +14,18 @@ import {
|
|||
} from '../boards_util';
|
||||
import boardStore from '~/boards/stores/boards_store';
|
||||
|
||||
import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql';
|
||||
import listsIssuesQuery from '../queries/lists_issues.query.graphql';
|
||||
import boardLabelsQuery from '../queries/board_labels.query.graphql';
|
||||
import createBoardListMutation from '../queries/board_list_create.mutation.graphql';
|
||||
import updateBoardListMutation from '../queries/board_list_update.mutation.graphql';
|
||||
import issueMoveListMutation from '../queries/issue_move_list.mutation.graphql';
|
||||
import destroyBoardListMutation from '../queries/board_list_destroy.mutation.graphql';
|
||||
import issueCreateMutation from '../queries/issue_create.mutation.graphql';
|
||||
import issueSetLabels from '../queries/issue_set_labels.mutation.graphql';
|
||||
import issueSetDueDate from '../queries/issue_set_due_date.mutation.graphql';
|
||||
import issueSetSubscriptionMutation from '../graphql/mutations/issue_set_subscription.mutation.graphql';
|
||||
import issueSetMilestone from '../queries/issue_set_milestone.mutation.graphql';
|
||||
import updateAssigneesMutation from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql';
|
||||
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
|
||||
import boardLabelsQuery from '../graphql/board_labels.query.graphql';
|
||||
import createBoardListMutation from '../graphql/board_list_create.mutation.graphql';
|
||||
import updateBoardListMutation from '../graphql/board_list_update.mutation.graphql';
|
||||
import issueMoveListMutation from '../graphql/issue_move_list.mutation.graphql';
|
||||
import destroyBoardListMutation from '../graphql/board_list_destroy.mutation.graphql';
|
||||
import issueCreateMutation from '../graphql/issue_create.mutation.graphql';
|
||||
import issueSetLabelsMutation from '../graphql/issue_set_labels.mutation.graphql';
|
||||
import issueSetDueDateMutation from '../graphql/issue_set_due_date.mutation.graphql';
|
||||
import issueSetSubscriptionMutation from '../graphql/issue_set_subscription.mutation.graphql';
|
||||
import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.graphql';
|
||||
|
||||
const notImplemented = () => {
|
||||
/* eslint-disable-next-line @gitlab/require-i18n-strings */
|
||||
|
|
@ -324,7 +324,7 @@ export default {
|
|||
|
||||
return gqlClient
|
||||
.mutate({
|
||||
mutation: updateAssignees,
|
||||
mutation: updateAssigneesMutation,
|
||||
variables: {
|
||||
iid: getters.activeIssue.iid,
|
||||
projectPath: getters.activeIssue.referencePath.split('#')[0],
|
||||
|
|
@ -350,7 +350,7 @@ export default {
|
|||
setActiveIssueMilestone: async ({ commit, getters }, input) => {
|
||||
const { activeIssue } = getters;
|
||||
const { data } = await gqlClient.mutate({
|
||||
mutation: issueSetMilestone,
|
||||
mutation: issueSetMilestoneMutation,
|
||||
variables: {
|
||||
input: {
|
||||
iid: String(activeIssue.iid),
|
||||
|
|
@ -416,7 +416,7 @@ export default {
|
|||
setActiveIssueLabels: async ({ commit, getters }, input) => {
|
||||
const { activeIssue } = getters;
|
||||
const { data } = await gqlClient.mutate({
|
||||
mutation: issueSetLabels,
|
||||
mutation: issueSetLabelsMutation,
|
||||
variables: {
|
||||
input: {
|
||||
iid: String(activeIssue.iid),
|
||||
|
|
@ -441,7 +441,7 @@ export default {
|
|||
setActiveIssueDueDate: async ({ commit, getters }, input) => {
|
||||
const { activeIssue } = getters;
|
||||
const { data } = await gqlClient.mutate({
|
||||
mutation: issueSetDueDate,
|
||||
mutation: issueSetDueDateMutation,
|
||||
variables: {
|
||||
input: {
|
||||
iid: String(activeIssue.iid),
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import ListLabel from '../models/label';
|
|||
import ListAssignee from '../models/assignee';
|
||||
import ListMilestone from '../models/milestone';
|
||||
|
||||
import createBoardMutation from '../queries/board.mutation.graphql';
|
||||
import createBoardMutation from '../graphql/board.mutation.graphql';
|
||||
|
||||
const PER_PAGE = 20;
|
||||
export const gqlClient = createDefaultClient();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { find } from 'lodash';
|
|||
import { inactiveId } from '../constants';
|
||||
|
||||
export default {
|
||||
labelToggleState: state => (state.isShowingLabels ? 'on' : 'off'),
|
||||
isSidebarOpen: state => state.activeId !== inactiveId,
|
||||
isSwimlanesOn: () => false,
|
||||
getIssueById: state => id => {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import notesEventHub from '../../notes/event_hub';
|
|||
import DiffFileHeader from './diff_file_header.vue';
|
||||
import DiffContent from './diff_content.vue';
|
||||
import { diffViewerErrors } from '~/ide/constants';
|
||||
import { collapsedType, isCollapsed } from '../diff_file';
|
||||
import { collapsedType, isCollapsed } from '../utils/diff_file';
|
||||
import {
|
||||
DIFF_FILE_AUTOMATIC_COLLAPSE,
|
||||
DIFF_FILE_MANUAL_COLLAPSE,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { __, s__, sprintf } from '~/locale';
|
|||
import { diffViewerModes } from '~/ide/constants';
|
||||
import DiffStats from './diff_stats.vue';
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
import { isCollapsed } from '../diff_file';
|
||||
import { isCollapsed } from '../utils/diff_file';
|
||||
import { DIFF_FILE_HEADER } from '../i18n';
|
||||
|
||||
export default {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import {
|
|||
DIFF_FILE_BY_FILE_COOKIE_NAME,
|
||||
} from '../constants';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
import { isCollapsed } from '../diff_file';
|
||||
import { isCollapsed } from '../utils/diff_file';
|
||||
|
||||
export const setBaseConfig = ({ commit }, options) => {
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
SHOW_WHITESPACE,
|
||||
NO_SHOW_WHITESPACE,
|
||||
} from '../constants';
|
||||
import { prepareRawDiffFile } from '../diff_file';
|
||||
import { prepareRawDiffFile } from '../utils/diff_file';
|
||||
|
||||
export const isAdded = line => ['new', 'new-nonewline'].includes(line.type);
|
||||
export const isRemoved = line => ['old', 'old-nonewline'].includes(line.type);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
DIFF_FILE_DELETED_MODE,
|
||||
DIFF_FILE_MANUAL_COLLAPSE,
|
||||
DIFF_FILE_AUTOMATIC_COLLAPSE,
|
||||
} from './constants';
|
||||
} from '../constants';
|
||||
|
||||
function fileSymlinkInformation(file, fileList) {
|
||||
const duplicates = fileList.filter(iteratedFile => iteratedFile.file_hash === file.file_hash);
|
||||
|
|
@ -86,7 +86,7 @@ export default {
|
|||
<form ref="settingsForm" @submit.prevent="updateAlertsIntegrationSettings">
|
||||
<gl-form-group class="gl-pl-0">
|
||||
<gl-form-checkbox v-model="createIssueEnabled" data-qa-selector="create_issue_checkbox">
|
||||
<span>{{ $options.i18n.createIssue.label }}</span>
|
||||
<span>{{ $options.i18n.createIncident.label }}</span>
|
||||
</gl-form-checkbox>
|
||||
</gl-form-group>
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ export default {
|
|||
class="col-8 col-md-9 gl-px-6"
|
||||
>
|
||||
<label class="gl-display-inline-flex" for="alert-integration-settings-issue-template">
|
||||
{{ $options.i18n.issueTemplate.label }}
|
||||
{{ $options.i18n.incidentTemplate.label }}
|
||||
<gl-link :href="$options.ISSUE_TEMPLATES_DOCS_LINK" target="_blank">
|
||||
<gl-icon name="question" :size="12" />
|
||||
</gl-link>
|
||||
|
|
|
|||
|
|
@ -109,7 +109,20 @@ export default {
|
|||
{{ webhookUpdateAlertMsg }}
|
||||
</gl-alert>
|
||||
|
||||
<p>{{ $options.i18n.introText }}</p>
|
||||
<p>
|
||||
<gl-sprintf :message="$options.i18n.introText">
|
||||
<template #link="{ content }">
|
||||
<gl-link
|
||||
:href="$options.CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK"
|
||||
target="_blank"
|
||||
class="gl-display-inline-flex"
|
||||
>
|
||||
<span>{{ content }}</span>
|
||||
<gl-icon name="external-link" />
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
<form ref="settingsForm" @submit.prevent="updatePagerDutyIntegrationSettings">
|
||||
<gl-form-group class="col-8 col-md-9 gl-p-0">
|
||||
<gl-toggle
|
||||
|
|
@ -134,23 +147,9 @@ export default {
|
|||
</template>
|
||||
</gl-form-input-group>
|
||||
|
||||
<div class="gl-text-gray-200 gl-pt-2">
|
||||
<gl-sprintf :message="$options.i18n.webhookUrl.helpText">
|
||||
<template #docsLink>
|
||||
<gl-link
|
||||
:href="$options.CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK"
|
||||
target="_blank"
|
||||
class="gl-display-inline-flex"
|
||||
>
|
||||
<span>{{ $options.i18n.webhookUrl.helpDocsLink }}</span>
|
||||
<gl-icon name="external-link" />
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<gl-button
|
||||
v-gl-modal.resetWebhookModal
|
||||
class="gl-mt-3"
|
||||
class="gl-mt-5"
|
||||
:disabled="loading"
|
||||
:loading="resettingWebhook"
|
||||
data-testid="webhook-reset-btn"
|
||||
|
|
|
|||
|
|
@ -33,17 +33,17 @@ export const I18N_ALERT_SETTINGS_FORM = {
|
|||
saveBtnLabel: __('Save changes'),
|
||||
introText: __('Action to take when receiving an alert. %{docsLink}'),
|
||||
introLinkText: __('More information.'),
|
||||
createIssue: {
|
||||
label: __('Create an issue. Issues are created for each alert triggered.'),
|
||||
createIncident: {
|
||||
label: __('Create an incident. Incidents are created for each alert triggered.'),
|
||||
},
|
||||
issueTemplate: {
|
||||
label: __('Issue template (optional)'),
|
||||
incidentTemplate: {
|
||||
label: __('Incident template (optional)'),
|
||||
},
|
||||
sendEmail: {
|
||||
label: __('Send a separate email notification to Developers.'),
|
||||
},
|
||||
autoCloseIncidents: {
|
||||
label: __('Automatically close incident issues when the associated Prometheus alert resolves.'),
|
||||
label: __('Automatically close incidents when the associated Prometheus alert resolves.'),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -57,17 +57,13 @@ export const ISSUE_TEMPLATES_DOCS_LINK =
|
|||
|
||||
export const I18N_PAGERDUTY_SETTINGS_FORM = {
|
||||
introText: s__(
|
||||
'PagerDutySettings|Setting up a webhook with PagerDuty will automatically create a GitLab issue for each PagerDuty incident.',
|
||||
'PagerDutySettings|Create a GitLab incident for each PagerDuty incident by %{linkStart}configuring a webhook in PagerDuty%{linkEnd}',
|
||||
),
|
||||
activeToggle: {
|
||||
label: s__('PagerDutySettings|Active'),
|
||||
},
|
||||
webhookUrl: {
|
||||
label: s__('PagerDutySettings|Webhook URL'),
|
||||
helpText: s__(
|
||||
'PagerDutySettings|Create a GitLab issue for each PagerDuty incident by %{docsLink}',
|
||||
),
|
||||
helpDocsLink: s__('PagerDutySettings|configuring a webhook in PagerDuty'),
|
||||
resetWebhookUrl: s__('PagerDutySettings|Reset webhook URL'),
|
||||
copyToClipboard: __('Copy'),
|
||||
updateErrMsg: s__('PagerDutySettings|Failed to update Webhook URL'),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ export default {
|
|||
showReset() {
|
||||
return this.isInstanceOrGroupLevel && this.propsSource.resetPath;
|
||||
},
|
||||
saveButtonKey() {
|
||||
return `save-button-${this.isDisabled}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
|
|
@ -117,6 +120,7 @@ export default {
|
|||
<div v-if="isEditable" class="footer-block row-content-block">
|
||||
<template v-if="isInstanceOrGroupLevel">
|
||||
<gl-button
|
||||
:key="saveButtonKey"
|
||||
v-gl-modal.confirmSaveIntegration
|
||||
category="primary"
|
||||
variant="success"
|
||||
|
|
@ -130,6 +134,7 @@ export default {
|
|||
</template>
|
||||
<gl-button
|
||||
v-else
|
||||
:key="saveButtonKey"
|
||||
category="primary"
|
||||
variant="success"
|
||||
type="submit"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
|||
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
|
||||
import { getDiffMode } from '~/diffs/store/utils';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
import { isCollapsed } from '../../diffs/diff_file';
|
||||
import { isCollapsed } from '../../diffs/utils/diff_file';
|
||||
|
||||
const FIRST_CHAR_REGEX = /^(\+|-| )/;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import { camelCase, difference, union } from 'lodash';
|
||||
import updateIssueLabelsMutation from '~/boards/queries/issue_set_labels.mutation.graphql';
|
||||
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
|
||||
import createFlash from '~/flash';
|
||||
import { IssuableType } from '~/issue_show/constants';
|
||||
import { __ } from '~/locale';
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@
|
|||
import { mapState, mapActions } from 'vuex';
|
||||
import {
|
||||
GlDrawer,
|
||||
GlBadge,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlInfiniteScroll,
|
||||
GlResizeObserverDirective,
|
||||
GlTabs,
|
||||
GlTab,
|
||||
GlBadge,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import SkeletonLoader from './skeleton_loader.vue';
|
||||
import Feature from './feature.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import { getDrawerBodyHeight } from '../utils/get_drawer_body_height';
|
||||
|
||||
|
|
@ -17,11 +19,13 @@ const trackingMixin = Tracking.mixin();
|
|||
export default {
|
||||
components: {
|
||||
GlDrawer,
|
||||
GlBadge,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlInfiniteScroll,
|
||||
GlTabs,
|
||||
GlTab,
|
||||
SkeletonLoader,
|
||||
Feature,
|
||||
GlBadge,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
directives: {
|
||||
GlResizeObserver: GlResizeObserverDirective,
|
||||
|
|
@ -31,11 +35,19 @@ export default {
|
|||
storageKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
versions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
gitlabDotCom: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['open', 'features', 'pageInfo', 'drawerBodyHeight']),
|
||||
...mapState(['open', 'features', 'pageInfo', 'drawerBodyHeight', 'fetching']),
|
||||
},
|
||||
mounted() {
|
||||
this.openDrawer(this.storageKey);
|
||||
|
|
@ -49,14 +61,25 @@ export default {
|
|||
methods: {
|
||||
...mapActions(['openDrawer', 'closeDrawer', 'fetchItems', 'setDrawerBodyHeight']),
|
||||
bottomReached() {
|
||||
if (this.pageInfo.nextPage) {
|
||||
this.fetchItems(this.pageInfo.nextPage);
|
||||
const page = this.pageInfo.nextPage;
|
||||
if (page) {
|
||||
this.fetchItems({ page });
|
||||
}
|
||||
},
|
||||
handleResize() {
|
||||
const height = getDrawerBodyHeight(this.$refs.drawer.$el);
|
||||
this.setDrawerBodyHeight(height);
|
||||
},
|
||||
featuresForVersion(version) {
|
||||
return this.features.filter(feature => {
|
||||
return feature.release === parseFloat(version);
|
||||
});
|
||||
},
|
||||
fetchVersion(version) {
|
||||
if (this.featuresForVersion(version).length === 0) {
|
||||
this.fetchItems({ version });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -73,64 +96,39 @@ export default {
|
|||
<template #header>
|
||||
<h4 class="page-title gl-my-2">{{ __("What's new at GitLab") }}</h4>
|
||||
</template>
|
||||
<gl-infinite-scroll
|
||||
v-if="features.length"
|
||||
:fetched-items="features.length"
|
||||
:max-list-height="drawerBodyHeight"
|
||||
class="gl-p-0"
|
||||
@bottomReached="bottomReached"
|
||||
>
|
||||
<template #items>
|
||||
<div
|
||||
v-for="feature in features"
|
||||
:key="feature.title"
|
||||
class="gl-pb-7 gl-pt-5 gl-px-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
|
||||
<template v-if="features.length">
|
||||
<gl-infinite-scroll
|
||||
v-if="gitlabDotCom"
|
||||
:fetched-items="features.length"
|
||||
:max-list-height="drawerBodyHeight"
|
||||
class="gl-p-0"
|
||||
@bottomReached="bottomReached"
|
||||
>
|
||||
<template #items>
|
||||
<feature v-for="feature in features" :key="feature.title" :feature="feature" />
|
||||
</template>
|
||||
</gl-infinite-scroll>
|
||||
<gl-tabs v-else :style="{ height: `${drawerBodyHeight}px` }" class="gl-p-0">
|
||||
<gl-tab
|
||||
v-for="(version, index) in versions"
|
||||
:key="version"
|
||||
@click="fetchVersion(version)"
|
||||
>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
class="whats-new-item-title-link"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>
|
||||
<h5 class="gl-font-lg">{{ feature.title }}</h5>
|
||||
</gl-link>
|
||||
<div v-if="feature.packages" class="gl-mb-3">
|
||||
<gl-badge
|
||||
v-for="package_name in feature.packages"
|
||||
:key="package_name"
|
||||
size="sm"
|
||||
class="whats-new-item-badge gl-mr-2"
|
||||
>
|
||||
<gl-icon name="license" />{{ package_name }}
|
||||
</gl-badge>
|
||||
</div>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>
|
||||
<img
|
||||
:alt="feature.title"
|
||||
:src="feature.image_url"
|
||||
class="img-thumbnail gl-px-8 gl-py-3 whats-new-item-image"
|
||||
<template #title>
|
||||
<span>{{ version }}</span>
|
||||
<gl-badge v-if="index === 0">{{ __('Your Version') }}</gl-badge>
|
||||
</template>
|
||||
<gl-loading-icon v-if="fetching" size="lg" class="text-center" />
|
||||
<template v-else>
|
||||
<feature
|
||||
v-for="feature in featuresForVersion(version)"
|
||||
:key="feature.title"
|
||||
:feature="feature"
|
||||
/>
|
||||
</gl-link>
|
||||
<p class="gl-pt-3">{{ feature.body }}</p>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>{{ __('Learn more') }}</gl-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</gl-infinite-scroll>
|
||||
</template>
|
||||
</gl-tab>
|
||||
</gl-tabs>
|
||||
</template>
|
||||
<div v-else class="gl-mt-5">
|
||||
<skeleton-loader />
|
||||
<skeleton-loader />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
<script>
|
||||
import { GlBadge, GlIcon, GlLink } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlBadge,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
},
|
||||
props: {
|
||||
feature: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-pb-7 gl-pt-5 gl-px-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100">
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
class="whats-new-item-title-link"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>
|
||||
<h5 class="gl-font-lg" data-test-id="feature-title">{{ feature.title }}</h5>
|
||||
</gl-link>
|
||||
<div v-if="feature.packages" class="gl-mb-3">
|
||||
<gl-badge
|
||||
v-for="packageName in feature.packages"
|
||||
:key="packageName"
|
||||
size="sm"
|
||||
class="whats-new-item-badge gl-mr-2"
|
||||
>
|
||||
<gl-icon name="license" />{{ packageName }}
|
||||
</gl-badge>
|
||||
</div>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>
|
||||
<img
|
||||
:alt="feature.title"
|
||||
:src="feature.image_url"
|
||||
class="img-thumbnail gl-px-8 gl-py-3 whats-new-item-image"
|
||||
/>
|
||||
</gl-link>
|
||||
<p class="gl-pt-3">{{ feature.body }}</p>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>{{ __('Learn more') }}</gl-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -10,8 +10,6 @@ export default el => {
|
|||
if (whatsNewApp) {
|
||||
store.dispatch('openDrawer');
|
||||
} else {
|
||||
const storageKey = getStorageKey(el);
|
||||
|
||||
whatsNewApp = new Vue({
|
||||
el,
|
||||
store,
|
||||
|
|
@ -28,7 +26,11 @@ export default el => {
|
|||
},
|
||||
render(createElement) {
|
||||
return createElement('app', {
|
||||
props: { storageKey },
|
||||
props: {
|
||||
storageKey: getStorageKey(el),
|
||||
versions: JSON.parse(el.getAttribute('data-versions')),
|
||||
gitlabDotCom: el.getAttribute('data-gitlab-dot-com'),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default {
|
|||
localStorage.setItem(storageKey, JSON.stringify(false));
|
||||
}
|
||||
},
|
||||
fetchItems({ commit, state }, page) {
|
||||
fetchItems({ commit, state }, { page, version } = { page: null, version: null }) {
|
||||
if (state.fetching) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ export default {
|
|||
.get('/-/whats_new', {
|
||||
params: {
|
||||
page,
|
||||
version,
|
||||
},
|
||||
})
|
||||
.then(({ data, headers }) => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,32 @@
|
|||
.gl-infinite-scroll-legend {
|
||||
@include gl-display-none;
|
||||
}
|
||||
|
||||
.gl-tabs {
|
||||
@include gl-overflow-y-auto;
|
||||
}
|
||||
|
||||
.gl-tabs-nav {
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: scroll;
|
||||
align-items: stretch;
|
||||
|
||||
.nav-item {
|
||||
@include gl-flex-shrink-0;
|
||||
|
||||
a {
|
||||
@include gl-h-full;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gl-spinner-container {
|
||||
@include gl-w-full;
|
||||
@include gl-absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.with-performance-bar .whats-new-drawer {
|
||||
|
|
|
|||
|
|
@ -28,3 +28,115 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// Copied from roadmaps.scss - adapted for on-call schedules
|
||||
$header-item-height: 60px;
|
||||
$details-cell-width: px-to-rem(150px);
|
||||
$timeline-cell-height: 32px;
|
||||
$timeline-cell-width: 180px;
|
||||
$border-style: 1px solid var(--gray-100, $gray-100);
|
||||
$gradient-dark-gray: rgba(0, 0, 0, 0.15);
|
||||
$gradient-gray: rgba(255, 255, 255, 0.001);
|
||||
$scroll-top-gradient: linear-gradient(to bottom, $gradient-dark-gray 0%, $gradient-gray 100%);
|
||||
$scroll-bottom-gradient: linear-gradient(to bottom, $gradient-gray 0%, $gradient-dark-gray 100%);
|
||||
$column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradient-gray 100%);
|
||||
$epic-details-cell-width: 150px;
|
||||
|
||||
.schedule-shell {
|
||||
@include gl-relative;
|
||||
@include gl-h-full;
|
||||
@include gl-w-full;
|
||||
@include gl-overflow-x-auto;
|
||||
@include gl-border-gray-100;
|
||||
@include gl-border-1;
|
||||
@include gl-border-solid;
|
||||
@include gl-rounded-base;
|
||||
}
|
||||
|
||||
.timeline-section {
|
||||
@include gl-sticky;
|
||||
position: -webkit-sticky;
|
||||
@include gl-top-0;
|
||||
z-index: 20;
|
||||
|
||||
.timeline-header-blank,
|
||||
.timeline-header-item {
|
||||
@include float-left;
|
||||
height: $header-item-height;
|
||||
border-bottom: $border-style;
|
||||
background-color: var(--white, $white);
|
||||
}
|
||||
|
||||
.timeline-header-blank {
|
||||
@include gl-sticky;
|
||||
position: -webkit-sticky;
|
||||
@include gl-top-0;
|
||||
@include gl-left-0;
|
||||
width: $details-cell-width;
|
||||
z-index: 2;
|
||||
|
||||
&::after {
|
||||
height: $header-item-height;
|
||||
@include gl-content-empty;
|
||||
@include gl-absolute;
|
||||
@include gl-top-0;
|
||||
right: -$grid-size;
|
||||
width: $grid-size;
|
||||
@include gl-pointer-events-none;
|
||||
background: $column-right-gradient;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-header-item {
|
||||
// container size minus left panel width divided by 2 week timeframes
|
||||
width: calc((100% - #{$epic-details-cell-width}) / 2);
|
||||
|
||||
&:last-of-type .item-label {
|
||||
@include gl-border-r-0;
|
||||
}
|
||||
|
||||
.item-label,
|
||||
.item-sublabel .sublabel-value {
|
||||
color: var(--gray-400, $gray-400);
|
||||
@include gl-font-weight-normal;
|
||||
|
||||
&.label-dark {
|
||||
@include gl-text-gray-900;
|
||||
}
|
||||
|
||||
&.label-bold {
|
||||
@include gl-font-weight-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.item-label {
|
||||
padding: $gl-padding-8 $gl-padding;
|
||||
border-right: $border-style;
|
||||
border-bottom: $border-style;
|
||||
}
|
||||
|
||||
.item-sublabel {
|
||||
@include gl-relative;
|
||||
@include gl-display-flex;
|
||||
|
||||
.sublabel-value {
|
||||
@include gl-flex-grow-1;
|
||||
@include gl-flex-basis-0;
|
||||
|
||||
text-align: center;
|
||||
font-size: $code-font-size;
|
||||
line-height: 1.5;
|
||||
padding: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.current-day-indicator-header {
|
||||
@include gl-bottom-0;
|
||||
height: $gl-vert-padding;
|
||||
width: $gl-vert-padding;
|
||||
background-color: var(--red-500, $red-500);
|
||||
border-radius: 50%;
|
||||
transform: translateX(-3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class WhatsNewController < ApplicationController
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
before_action :check_feature_flag, :check_valid_page_param, :set_pagination_headers
|
||||
before_action :check_feature_flag
|
||||
before_action :check_valid_page_param, :set_pagination_headers, unless: -> { has_version_param? }
|
||||
|
||||
feature_category :navigation
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render json: most_recent_items
|
||||
render json: highlight_items
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -29,15 +32,25 @@ class WhatsNewController < ApplicationController
|
|||
params[:page]&.to_i || 1
|
||||
end
|
||||
|
||||
def most_recent
|
||||
@most_recent ||= ReleaseHighlight.paginated(page: current_page)
|
||||
def highlights
|
||||
strong_memoize(:highlights) do
|
||||
if has_version_param?
|
||||
ReleaseHighlight.for_version(version: params[:version])
|
||||
else
|
||||
ReleaseHighlight.paginated(page: current_page)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def most_recent_items
|
||||
most_recent[:items].map {|item| Gitlab::WhatsNew::ItemPresenter.present(item) }
|
||||
def highlight_items
|
||||
highlights.map {|item| Gitlab::WhatsNew::ItemPresenter.present(item) }
|
||||
end
|
||||
|
||||
def set_pagination_headers
|
||||
response.set_header('X-Next-Page', most_recent[:next_page])
|
||||
response.set_header('X-Next-Page', highlights.next_page)
|
||||
end
|
||||
|
||||
def has_version_param?
|
||||
params[:version].present?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@ module WhatsNewHelper
|
|||
end
|
||||
|
||||
def whats_new_storage_key
|
||||
most_recent_version = ReleaseHighlight.most_recent_version
|
||||
most_recent_version = ReleaseHighlight.versions&.first
|
||||
|
||||
return unless most_recent_version
|
||||
|
||||
['display-whats-new-notification', most_recent_version].join('-')
|
||||
end
|
||||
|
||||
def whats_new_versions
|
||||
ReleaseHighlight.versions
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,17 @@
|
|||
class ReleaseHighlight
|
||||
CACHE_DURATION = 1.hour
|
||||
FILES_PATH = Rails.root.join('data', 'whats_new', '*.yml')
|
||||
RELEASE_VERSIONS_IN_A_YEAR = 12
|
||||
|
||||
def self.for_version(version:)
|
||||
index = self.versions.index(version)
|
||||
|
||||
return if index.nil?
|
||||
|
||||
page = index + 1
|
||||
|
||||
self.paginated(page: page)
|
||||
end
|
||||
|
||||
def self.paginated(page: 1)
|
||||
Rails.cache.fetch(cache_key(page), expires_in: CACHE_DURATION) do
|
||||
|
|
@ -10,10 +21,7 @@ class ReleaseHighlight
|
|||
|
||||
next if items.nil?
|
||||
|
||||
{
|
||||
items: items,
|
||||
next_page: next_page(current_page: page)
|
||||
}
|
||||
QueryResult.new(items: items, next_page: next_page(current_page: page))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -53,15 +61,25 @@ class ReleaseHighlight
|
|||
next_page if self.file_paths[next_index]
|
||||
end
|
||||
|
||||
def self.most_recent_version
|
||||
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:release_version', expires_in: CACHE_DURATION) do
|
||||
self.paginated&.[](:items)&.first&.[]('release')
|
||||
def self.most_recent_item_count
|
||||
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:recent_item_count', expires_in: CACHE_DURATION) do
|
||||
self.paginated&.items&.count
|
||||
end
|
||||
end
|
||||
|
||||
def self.most_recent_item_count
|
||||
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:recent_item_count', expires_in: CACHE_DURATION) do
|
||||
self.paginated&.[](:items)&.count
|
||||
def self.versions
|
||||
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:versions', expires_in: CACHE_DURATION) do
|
||||
versions = self.file_paths.first(RELEASE_VERSIONS_IN_A_YEAR).map do |path|
|
||||
/\d*\_(\d*\_\d*)\.yml$/.match(path).captures[0].gsub(/0(?=\d)/, "").tr("_", ".")
|
||||
end
|
||||
|
||||
versions.uniq
|
||||
end
|
||||
end
|
||||
|
||||
QueryResult = Struct.new(:items, :next_page, keyword_init: true) do
|
||||
include Enumerable
|
||||
|
||||
delegate :each, to: :items
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ class Service < ApplicationRecord
|
|||
scope :by_type, -> (type) { where(type: type) }
|
||||
scope :by_active_flag, -> (flag) { where(active: flag) }
|
||||
scope :inherit_from_id, -> (id) { where(inherit_from_id: id) }
|
||||
scope :inherit, -> { where.not(inherit_from_id: nil) }
|
||||
scope :for_group, -> (group) { where(group_id: group, type: available_services_types(include_project_specific: false)) }
|
||||
scope :for_template, -> { where(template: true, type: available_services_types(include_project_specific: false)) }
|
||||
scope :for_instance, -> { where(instance: true, type: available_services_types(include_project_specific: false)) }
|
||||
|
|
@ -278,7 +279,7 @@ class Service < ApplicationRecord
|
|||
active.where(instance: true),
|
||||
active.where(group_id: group_ids, inherit_from_id: nil)
|
||||
]).order(Arel.sql("type ASC, array_position(#{array}::bigint[], services.group_id), instance DESC")).group_by(&:type).each do |type, records|
|
||||
build_from_integration(records.first, association => scope.id).save!
|
||||
build_from_integration(records.first, association => scope.id).save
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ module Groups
|
|||
Group.transaction do
|
||||
update_group_attributes
|
||||
ensure_ownership
|
||||
update_integrations
|
||||
end
|
||||
|
||||
post_update_hooks(@updated_project_ids)
|
||||
propagate_integrations
|
||||
|
||||
true
|
||||
end
|
||||
|
|
@ -196,6 +198,17 @@ module Groups
|
|||
raise TransferError, result[:message] unless result[:status] == :success
|
||||
end
|
||||
end
|
||||
|
||||
def update_integrations
|
||||
@group.services.inherit.delete_all
|
||||
Service.create_from_active_default_integrations(@group, :group_id)
|
||||
end
|
||||
|
||||
def propagate_integrations
|
||||
@group.services.inherit.each do |integration|
|
||||
PropagateIntegrationWorker.perform_async(integration.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ module Projects
|
|||
raise TransferError.new(s_("TransferProject|Root namespace can't be updated if project has NPM packages"))
|
||||
end
|
||||
|
||||
attempt_transfer_transaction
|
||||
proceed_to_transfer
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ module Projects
|
|||
new_namespace.root_ancestor == project.namespace.root_ancestor
|
||||
end
|
||||
|
||||
def attempt_transfer_transaction
|
||||
def proceed_to_transfer
|
||||
Project.transaction do
|
||||
project.expire_caches_before_rename(@old_path)
|
||||
|
||||
|
|
@ -87,6 +87,8 @@ module Projects
|
|||
# Move uploads
|
||||
move_project_uploads(project)
|
||||
|
||||
update_integrations
|
||||
|
||||
project.old_path_with_namespace = @old_path
|
||||
|
||||
update_repository_configuration(@new_path)
|
||||
|
|
@ -214,6 +216,11 @@ module Projects
|
|||
project.shared_runners_enabled = false
|
||||
end
|
||||
end
|
||||
|
||||
def update_integrations
|
||||
project.services.inherit.delete_all
|
||||
Service.create_from_active_default_integrations(project, :project_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@
|
|||
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
|
||||
|
||||
- if ::Feature.enabled?(:whats_new_drawer, current_user)
|
||||
#whats-new-app{ data: { storage_key: whats_new_storage_key } }
|
||||
#whats-new-app{ data: { storage_key: whats_new_storage_key, versions: whats_new_versions, gitlab_dot_com: Gitlab.dev_env_org_or_com? } }
|
||||
|
||||
- if can?(current_user, :update_user_status, current_user)
|
||||
.js-set-status-modal-wrapper{ data: user_status_data }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Transfer a project/group to a new namespace inheriting integrations
|
||||
merge_request: 48621
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Resolve Save button should have a different color on press
|
||||
merge_request: 48975
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use incident instead of issue for operation settings
|
||||
merge_request: 48406
|
||||
author:
|
||||
type: fixed
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 102 KiB |
|
|
@ -5,7 +5,10 @@
|
|||
# Utility module for A/B testing experimental features. Define your experiments in the `EXPERIMENTS` constant.
|
||||
# Experiment options:
|
||||
# - tracking_category (optional, used to set the category when tracking an experiment event)
|
||||
# - use_backwards_compatible_subject_index (optional, set this to true if you need backwards compatibility)
|
||||
# - use_backwards_compatible_subject_index (optional, set this to true if you need backwards compatibility -- you likely do not need this, see note in the next paragraph.)
|
||||
#
|
||||
# Using the backwards-compatible subject index (use_backwards_compatible_subject_index option):
|
||||
# This option was added when [the calculation of experimentation_subject_index was changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45733/diffs#41af4a6fa5a10c7068559ce21c5188483751d934_157_173). It is not intended to be used by new experiments, it exists merely for the segmentation integrity of in-flight experiments at the time the change was deployed. That is, we want users who were assigned to the "experimental" group or the "control" group before the change to still be in those same groups after the change. See [the original issue](https://gitlab.com/gitlab-org/gitlab/-/issues/270858) and [this related comment](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48110#note_458223745) for more information.
|
||||
#
|
||||
# The experiment is controlled by a Feature Flag (https://docs.gitlab.com/ee/development/feature_flags/controls.html),
|
||||
# which is named "#{experiment_key}_experiment_percentage" and *must* be set with a percentage and not be used for other purposes.
|
||||
|
|
|
|||
|
|
@ -941,6 +941,9 @@ msgstr ""
|
|||
msgid "(No changes)"
|
||||
msgstr ""
|
||||
|
||||
msgid "(UTC%{offset}) %{timezone}"
|
||||
msgstr ""
|
||||
|
||||
msgid "(check progress)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -4169,7 +4172,7 @@ msgstr ""
|
|||
msgid "Automatic deployment rollbacks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically close incident issues when the associated Prometheus alert resolves."
|
||||
msgid "Automatically close incidents when the associated Prometheus alert resolves."
|
||||
msgstr ""
|
||||
|
||||
msgid "Automatically create merge requests for vulnerabilities that have fixes available."
|
||||
|
|
@ -7919,7 +7922,7 @@ msgstr ""
|
|||
msgid "Create an account using:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create an issue. Issues are created for each alert triggered."
|
||||
msgid "Create an incident. Incidents are created for each alert triggered."
|
||||
msgstr ""
|
||||
|
||||
msgid "Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the %{code_open}repo%{code_close} scope, so we can display a list of your public and private repositories which are available to import."
|
||||
|
|
@ -14377,6 +14380,9 @@ msgstr ""
|
|||
msgid "Incident Management Limits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Incident template (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "IncidentManagement|%{hours} hours, %{minutes} minutes remaining"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15207,9 +15213,6 @@ msgstr ""
|
|||
msgid "Issue published on status page."
|
||||
msgstr ""
|
||||
|
||||
msgid "Issue template (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Issue update failed"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -19112,6 +19115,12 @@ msgstr ""
|
|||
msgid "OnCallSchedules|Failed to add schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|On-call schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|On-call schedule for the %{tzShort}"
|
||||
msgstr ""
|
||||
|
||||
msgid "OnCallSchedules|Rotation length"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -19729,7 +19738,7 @@ msgstr ""
|
|||
msgid "PagerDutySettings|Active"
|
||||
msgstr ""
|
||||
|
||||
msgid "PagerDutySettings|Create a GitLab issue for each PagerDuty incident by %{docsLink}"
|
||||
msgid "PagerDutySettings|Create a GitLab incident for each PagerDuty incident by %{linkStart}configuring a webhook in PagerDuty%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "PagerDutySettings|Failed to update Webhook URL"
|
||||
|
|
@ -19741,18 +19750,12 @@ msgstr ""
|
|||
msgid "PagerDutySettings|Resetting the webhook URL for this project will require updating this integration's settings in PagerDuty."
|
||||
msgstr ""
|
||||
|
||||
msgid "PagerDutySettings|Setting up a webhook with PagerDuty will automatically create a GitLab issue for each PagerDuty incident."
|
||||
msgstr ""
|
||||
|
||||
msgid "PagerDutySettings|Webhook URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "PagerDutySettings|Webhook URL update was successful"
|
||||
msgstr ""
|
||||
|
||||
msgid "PagerDutySettings|configuring a webhook in PagerDuty"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pages"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -25174,6 +25177,9 @@ msgstr ""
|
|||
msgid "Show file contents"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show labels"
|
||||
msgstr ""
|
||||
|
||||
msgid "Show latest version"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31711,6 +31717,9 @@ msgstr ""
|
|||
msgid "Your U2F device was registered!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your WebAuthn device did not send a valid JSON response."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
|
|||
|
||||
describe 'Settings > Operations' do
|
||||
describe 'Incidents' do
|
||||
let(:create_issue) { 'Create an issue. Issues are created for each alert triggered.' }
|
||||
let(:create_issue) { 'Create an incident. Incidents are created for each alert triggered.' }
|
||||
let(:send_email) { 'Send a separate email notification to Developers.' }
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dro
|
|||
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
|
||||
import store from '~/boards/stores';
|
||||
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/getIssueParticipants.query.graphql';
|
||||
import searchUsers from '~/boards/queries/users_search.query.graphql';
|
||||
import searchUsers from '~/boards/graphql/users_search.query.graphql';
|
||||
import { participants } from '../mock_data';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import {
|
|||
import actions, { gqlClient } from '~/boards/stores/actions';
|
||||
import * as types from '~/boards/stores/mutation_types';
|
||||
import { inactiveId } from '~/boards/constants';
|
||||
import issueMoveListMutation from '~/boards/queries/issue_move_list.mutation.graphql';
|
||||
import destroyBoardListMutation from '~/boards/queries/board_list_destroy.mutation.graphql';
|
||||
import issueMoveListMutation from '~/boards/graphql/issue_move_list.mutation.graphql';
|
||||
import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql';
|
||||
import updateAssignees from '~/vue_shared/components/sidebar/queries/updateAssignees.mutation.graphql';
|
||||
import { fullBoardId, formatListIssues, formatBoardLists } from '~/boards/boards_util';
|
||||
|
||||
|
|
|
|||
|
|
@ -10,24 +10,6 @@ import {
|
|||
} from '../mock_data';
|
||||
|
||||
describe('Boards - Getters', () => {
|
||||
describe('labelToggleState', () => {
|
||||
it('should return "on" when isShowingLabels is true', () => {
|
||||
const state = {
|
||||
isShowingLabels: true,
|
||||
};
|
||||
|
||||
expect(getters.labelToggleState(state)).toBe('on');
|
||||
});
|
||||
|
||||
it('should return "off" when isShowingLabels is false', () => {
|
||||
const state = {
|
||||
isShowingLabels: false,
|
||||
};
|
||||
|
||||
expect(getters.labelToggleState(state)).toBe('off');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSidebarOpen', () => {
|
||||
it('returns true when activeId is not equal to 0', () => {
|
||||
const state = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { prepareRawDiffFile } from '~/diffs/diff_file';
|
||||
import { prepareRawDiffFile } from '~/diffs/utils/diff_file';
|
||||
|
||||
const DIFF_FILES = [
|
||||
{
|
||||
|
|
@ -17,7 +17,7 @@ exports[`Alert integration settings form default state should match the default
|
|||
data-qa-selector="create_issue_checkbox"
|
||||
>
|
||||
<span>
|
||||
Create an issue. Issues are created for each alert triggered.
|
||||
Create an incident. Incidents are created for each alert triggered.
|
||||
</span>
|
||||
</gl-form-checkbox-stub>
|
||||
</gl-form-group-stub>
|
||||
|
|
@ -32,7 +32,7 @@ exports[`Alert integration settings form default state should match the default
|
|||
for="alert-integration-settings-issue-template"
|
||||
>
|
||||
|
||||
Issue template (optional)
|
||||
Incident template (optional)
|
||||
|
||||
<gl-link-stub
|
||||
href="/help/user/project/description_templates#creating-issue-templates"
|
||||
|
|
@ -89,7 +89,7 @@ exports[`Alert integration settings form default state should match the default
|
|||
checked="true"
|
||||
>
|
||||
<span>
|
||||
Automatically close incident issues when the associated Prometheus alert resolves.
|
||||
Automatically close incidents when the associated Prometheus alert resolves.
|
||||
</span>
|
||||
</gl-form-checkbox-stub>
|
||||
</gl-form-group-stub>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
|
|||
<!---->
|
||||
|
||||
<p>
|
||||
Setting up a webhook with PagerDuty will automatically create a GitLab issue for each PagerDuty incident.
|
||||
<gl-sprintf-stub
|
||||
message="Create a GitLab incident for each PagerDuty incident by %{linkStart}configuring a webhook in PagerDuty%{linkEnd}"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<form>
|
||||
|
|
@ -33,18 +35,10 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
|
|||
value="pagerduty.webhook.com"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="gl-text-gray-200 gl-pt-2"
|
||||
>
|
||||
<gl-sprintf-stub
|
||||
message="Create a GitLab issue for each PagerDuty incident by %{docsLink}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<gl-button-stub
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
class="gl-mt-3"
|
||||
class="gl-mt-5"
|
||||
data-testid="webhook-reset-btn"
|
||||
icon=""
|
||||
role="button"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
mockLabels,
|
||||
mockRegularLabel,
|
||||
} from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
|
||||
import updateIssueLabelsMutation from '~/boards/queries/issue_set_labels.mutation.graphql';
|
||||
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
|
||||
import { MutationOperationMode } from '~/graphql_shared/utils';
|
||||
import { IssuableType } from '~/issue_show/constants';
|
||||
import SidebarLabels from '~/sidebar/components/labels/sidebar_labels.vue';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import { GlDrawer, GlInfiniteScroll } from '@gitlab/ui';
|
||||
import { GlDrawer, GlInfiniteScroll, GlTabs } from '@gitlab/ui';
|
||||
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import App from '~/whats_new/components/app.vue';
|
||||
|
|
@ -16,12 +16,18 @@ const localVue = createLocalVue();
|
|||
localVue.use(Vuex);
|
||||
|
||||
describe('App', () => {
|
||||
const propsData = { storageKey: 'storage-key' };
|
||||
let wrapper;
|
||||
let store;
|
||||
let actions;
|
||||
let state;
|
||||
let trackingSpy;
|
||||
let gitlabDotCom = true;
|
||||
|
||||
const buildProps = () => ({
|
||||
storageKey: 'storage-key',
|
||||
versions: ['3.11', '3.10'],
|
||||
gitlabDotCom,
|
||||
});
|
||||
|
||||
const buildWrapper = () => {
|
||||
actions = {
|
||||
|
|
@ -45,7 +51,7 @@ describe('App', () => {
|
|||
wrapper = mount(App, {
|
||||
localVue,
|
||||
store,
|
||||
propsData,
|
||||
propsData: buildProps(),
|
||||
directives: {
|
||||
GlResizeObserver: createMockDirective(),
|
||||
},
|
||||
|
|
@ -53,112 +59,171 @@ describe('App', () => {
|
|||
};
|
||||
|
||||
const findInfiniteScroll = () => wrapper.find(GlInfiniteScroll);
|
||||
const emitBottomReached = () => findInfiniteScroll().vm.$emit('bottomReached');
|
||||
|
||||
beforeEach(async () => {
|
||||
const setup = async () => {
|
||||
document.body.dataset.page = 'test-page';
|
||||
document.body.dataset.namespaceId = 'namespace-840';
|
||||
|
||||
trackingSpy = mockTracking('_category_', null, jest.spyOn);
|
||||
buildWrapper();
|
||||
|
||||
wrapper.vm.$store.state.features = [{ title: 'Whats New Drawer', url: 'www.url.com' }];
|
||||
wrapper.vm.$store.state.features = [
|
||||
{ title: 'Whats New Drawer', url: 'www.url.com', release: 3.11 },
|
||||
];
|
||||
wrapper.vm.$store.state.drawerBodyHeight = MOCK_DRAWER_BODY_HEIGHT;
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
const getDrawer = () => wrapper.find(GlDrawer);
|
||||
|
||||
it('contains a drawer', () => {
|
||||
expect(getDrawer().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('dispatches openDrawer and tracking calls when mounted', () => {
|
||||
expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key');
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_whats_new_drawer', {
|
||||
label: 'namespace_id',
|
||||
value: 'namespace-840',
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches closeDrawer when clicking close', () => {
|
||||
getDrawer().vm.$emit('close');
|
||||
expect(actions.closeDrawer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([true, false])('passes open property', async openState => {
|
||||
wrapper.vm.$store.state.open = openState;
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(getDrawer().props('open')).toBe(openState);
|
||||
});
|
||||
|
||||
it('renders features when provided via ajax', () => {
|
||||
expect(actions.fetchItems).toHaveBeenCalled();
|
||||
expect(wrapper.find('h5').text()).toBe('Whats New Drawer');
|
||||
});
|
||||
|
||||
it('send an event when feature item is clicked', () => {
|
||||
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
|
||||
const link = wrapper.find('.whats-new-item-title-link');
|
||||
triggerEvent(link.element);
|
||||
|
||||
expect(trackingSpy.mock.calls[1]).toMatchObject([
|
||||
'_category_',
|
||||
'click_whats_new_item',
|
||||
{
|
||||
label: 'Whats New Drawer',
|
||||
property: 'www.url.com',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders infinite scroll', () => {
|
||||
const scroll = findInfiniteScroll();
|
||||
|
||||
expect(scroll.props()).toMatchObject({
|
||||
fetchedItems: wrapper.vm.$store.state.features.length,
|
||||
maxListHeight: MOCK_DRAWER_BODY_HEIGHT,
|
||||
});
|
||||
});
|
||||
|
||||
describe('bottomReached', () => {
|
||||
describe('gitlab.com', () => {
|
||||
beforeEach(() => {
|
||||
actions.fetchItems.mockClear();
|
||||
setup();
|
||||
});
|
||||
|
||||
it('when nextPage exists it calls fetchItems', () => {
|
||||
wrapper.vm.$store.state.pageInfo = { nextPage: 840 };
|
||||
emitBottomReached();
|
||||
const getDrawer = () => wrapper.find(GlDrawer);
|
||||
|
||||
expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), 840);
|
||||
it('contains a drawer', () => {
|
||||
expect(getDrawer().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('when nextPage does not exist it does not call fetchItems', () => {
|
||||
wrapper.vm.$store.state.pageInfo = { nextPage: null };
|
||||
emitBottomReached();
|
||||
it('dispatches openDrawer and tracking calls when mounted', () => {
|
||||
expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key');
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_whats_new_drawer', {
|
||||
label: 'namespace_id',
|
||||
value: 'namespace-840',
|
||||
});
|
||||
});
|
||||
|
||||
expect(actions.fetchItems).not.toHaveBeenCalled();
|
||||
it('dispatches closeDrawer when clicking close', () => {
|
||||
getDrawer().vm.$emit('close');
|
||||
expect(actions.closeDrawer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([true, false])('passes open property', async openState => {
|
||||
wrapper.vm.$store.state.open = openState;
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(getDrawer().props('open')).toBe(openState);
|
||||
});
|
||||
|
||||
it('renders features when provided via ajax', () => {
|
||||
expect(actions.fetchItems).toHaveBeenCalled();
|
||||
expect(wrapper.find('[data-test-id="feature-title"]').text()).toBe('Whats New Drawer');
|
||||
});
|
||||
|
||||
it('send an event when feature item is clicked', () => {
|
||||
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
|
||||
const link = wrapper.find('.whats-new-item-title-link');
|
||||
triggerEvent(link.element);
|
||||
|
||||
expect(trackingSpy.mock.calls[1]).toMatchObject([
|
||||
'_category_',
|
||||
'click_whats_new_item',
|
||||
{
|
||||
label: 'Whats New Drawer',
|
||||
property: 'www.url.com',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders infinite scroll', () => {
|
||||
const scroll = findInfiniteScroll();
|
||||
|
||||
expect(scroll.props()).toMatchObject({
|
||||
fetchedItems: wrapper.vm.$store.state.features.length,
|
||||
maxListHeight: MOCK_DRAWER_BODY_HEIGHT,
|
||||
});
|
||||
});
|
||||
|
||||
describe('bottomReached', () => {
|
||||
const emitBottomReached = () => findInfiniteScroll().vm.$emit('bottomReached');
|
||||
|
||||
beforeEach(() => {
|
||||
actions.fetchItems.mockClear();
|
||||
});
|
||||
|
||||
it('when nextPage exists it calls fetchItems', () => {
|
||||
wrapper.vm.$store.state.pageInfo = { nextPage: 840 };
|
||||
emitBottomReached();
|
||||
|
||||
expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), { page: 840 });
|
||||
});
|
||||
|
||||
it('when nextPage does not exist it does not call fetchItems', () => {
|
||||
wrapper.vm.$store.state.pageInfo = { nextPage: null };
|
||||
emitBottomReached();
|
||||
|
||||
expect(actions.fetchItems).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered', () => {
|
||||
const { value } = getBinding(getDrawer().element, 'gl-resize-observer');
|
||||
|
||||
value();
|
||||
|
||||
expect(getDrawerBodyHeight).toHaveBeenCalledWith(wrapper.find(GlDrawer).element);
|
||||
|
||||
expect(actions.setDrawerBodyHeight).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
MOCK_DRAWER_BODY_HEIGHT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered', () => {
|
||||
const { value } = getBinding(getDrawer().element, 'gl-resize-observer');
|
||||
describe('self managed', () => {
|
||||
const findTabs = () => wrapper.find(GlTabs);
|
||||
|
||||
value();
|
||||
const clickSecondTab = async () => {
|
||||
const secondTab = wrapper.findAll('.nav-link').at(1);
|
||||
await secondTab.trigger('click');
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
};
|
||||
|
||||
expect(getDrawerBodyHeight).toHaveBeenCalledWith(wrapper.find(GlDrawer).element);
|
||||
beforeEach(() => {
|
||||
gitlabDotCom = false;
|
||||
setup();
|
||||
});
|
||||
|
||||
expect(actions.setDrawerBodyHeight).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
MOCK_DRAWER_BODY_HEIGHT,
|
||||
);
|
||||
it('renders tabs with drawer body height and content', () => {
|
||||
const scroll = findInfiniteScroll();
|
||||
const tabs = findTabs();
|
||||
|
||||
expect(scroll.exists()).toBe(false);
|
||||
expect(tabs.attributes().style).toBe(`height: ${MOCK_DRAWER_BODY_HEIGHT}px;`);
|
||||
expect(wrapper.find('h5').text()).toBe('Whats New Drawer');
|
||||
});
|
||||
|
||||
describe('fetchVersion', () => {
|
||||
beforeEach(() => {
|
||||
actions.fetchItems.mockClear();
|
||||
});
|
||||
|
||||
it('when version isnt fetched, clicking a tab calls fetchItems', async () => {
|
||||
const fetchVersionSpy = jest.spyOn(wrapper.vm, 'fetchVersion');
|
||||
await clickSecondTab();
|
||||
|
||||
expect(fetchVersionSpy).toHaveBeenCalledWith('3.10');
|
||||
expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), { version: '3.10' });
|
||||
});
|
||||
|
||||
it('when version has been fetched, clicking a tab calls fetchItems', async () => {
|
||||
wrapper.vm.$store.state.features.push({ title: 'GitLab Stories', release: 3.1 });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const fetchVersionSpy = jest.spyOn(wrapper.vm, 'fetchVersion');
|
||||
await clickSecondTab();
|
||||
|
||||
expect(fetchVersionSpy).toHaveBeenCalledWith('3.10');
|
||||
expect(actions.fetchItems).not.toHaveBeenCalled();
|
||||
expect(wrapper.find('.tab-pane.active h5').text()).toBe('GitLab Stories');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,6 +41,23 @@ describe('whats new actions', () => {
|
|||
axiosMock.restore();
|
||||
});
|
||||
|
||||
it('passes arguments', () => {
|
||||
axiosMock.reset();
|
||||
|
||||
axiosMock
|
||||
.onGet('/-/whats_new', { params: { page: 8, version: 40 } })
|
||||
.replyOnce(200, [{ title: 'GitLab Stories' }]);
|
||||
|
||||
testAction(
|
||||
actions.fetchItems,
|
||||
{ page: 8, version: 40 },
|
||||
{},
|
||||
expect.arrayContaining([
|
||||
{ type: types.ADD_FEATURES, payload: [{ title: 'GitLab Stories' }] },
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('if already fetching, does not fetch', () => {
|
||||
testAction(actions.fetchItems, {}, { fetching: true }, []);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ RSpec.describe WhatsNewHelper do
|
|||
let(:release_item) { double(:item) }
|
||||
|
||||
before do
|
||||
allow(ReleaseHighlight).to receive(:most_recent_version).and_return(84.0)
|
||||
allow(ReleaseHighlight).to receive(:versions).and_return([84.0])
|
||||
end
|
||||
|
||||
it { is_expected.to eq('display-whats-new-notification-84.0') }
|
||||
|
|
@ -18,7 +18,7 @@ RSpec.describe WhatsNewHelper do
|
|||
|
||||
context 'when most recent release highlights do NOT exist' do
|
||||
before do
|
||||
allow(ReleaseHighlight).to receive(:most_recent_version).and_return(nil)
|
||||
allow(ReleaseHighlight).to receive(:versions).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
|
|
@ -44,4 +44,14 @@ RSpec.describe WhatsNewHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#whats_new_versions' do
|
||||
let(:versions) { [84.0] }
|
||||
|
||||
it 'returns ReleaseHighlight.versions' do
|
||||
expect(ReleaseHighlight).to receive(:versions).and_return(versions)
|
||||
|
||||
expect(helper.whats_new_versions).to eq(versions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,21 +3,44 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ReleaseHighlight do
|
||||
describe '#paginated' do
|
||||
let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
|
||||
let(:cache_mock) { double(:cache_mock) }
|
||||
let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
|
||||
let(:cache_mock) { double(:cache_mock) }
|
||||
|
||||
before do
|
||||
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
|
||||
allow(cache_mock).to receive(:fetch).with('release_highlight:file_paths', expires_in: 1.hour).and_yield
|
||||
end
|
||||
|
||||
after do
|
||||
ReleaseHighlight.instance_variable_set(:@file_paths, nil)
|
||||
end
|
||||
|
||||
describe '.for_version' do
|
||||
subject { ReleaseHighlight.for_version(version: version) }
|
||||
|
||||
let(:version) { '1.1' }
|
||||
|
||||
context 'with version param that exists' do
|
||||
it 'returns items from that version' do
|
||||
expect(subject.items.first['title']).to eq("It's gonna be a bright")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with version param that does NOT exist' do
|
||||
let(:version) { '84.0' }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.paginated' do
|
||||
let(:dot_com) { false }
|
||||
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(dot_com)
|
||||
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
|
||||
|
||||
expect(Rails).to receive(:cache).twice.and_return(cache_mock)
|
||||
expect(cache_mock).to receive(:fetch).with('release_highlight:file_paths', expires_in: 1.hour).and_yield
|
||||
end
|
||||
|
||||
after do
|
||||
ReleaseHighlight.instance_variable_set(:@file_paths, nil)
|
||||
end
|
||||
|
||||
context 'with page param' do
|
||||
|
|
@ -90,35 +113,12 @@ RSpec.describe ReleaseHighlight do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.most_recent_version' do
|
||||
subject { ReleaseHighlight.most_recent_version }
|
||||
|
||||
context 'when version exist' do
|
||||
let(:release_item) { double(:item) }
|
||||
|
||||
before do
|
||||
allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [release_item] })
|
||||
allow(release_item).to receive(:[]).with('release').and_return(84.0)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(84.0) }
|
||||
end
|
||||
|
||||
context 'when most recent release highlights do NOT exist' do
|
||||
before do
|
||||
allow(ReleaseHighlight).to receive(:paginated).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#most_recent_item_count' do
|
||||
describe '.most_recent_item_count' do
|
||||
subject { ReleaseHighlight.most_recent_item_count }
|
||||
|
||||
context 'when recent release items exist' do
|
||||
it 'returns the count from the most recent file' do
|
||||
allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [double(:item)] })
|
||||
allow(ReleaseHighlight).to receive(:paginated).and_return(double(:paginated, items: [double(:item)]))
|
||||
|
||||
expect(subject).to eq(1)
|
||||
end
|
||||
|
|
@ -132,4 +132,32 @@ RSpec.describe ReleaseHighlight do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.versions' do
|
||||
it 'returns versions from the file paths' do
|
||||
expect(ReleaseHighlight.versions).to eq(['1.5', '1.2', '1.1'])
|
||||
end
|
||||
|
||||
context 'when there are more than 12 versions' do
|
||||
let(:file_paths) do
|
||||
i = 0
|
||||
Array.new(20) { "20201225_01_#{i += 1}.yml" }
|
||||
end
|
||||
|
||||
it 'limits to 12 versions' do
|
||||
allow(ReleaseHighlight).to receive(:file_paths).and_return(file_paths)
|
||||
expect(ReleaseHighlight.versions.count).to eq(12)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'QueryResult' do
|
||||
subject { ReleaseHighlight::QueryResult.new(items: items, next_page: 2) }
|
||||
|
||||
let(:items) { [:item] }
|
||||
|
||||
it 'responds to map' do
|
||||
expect(subject.map(&:to_s)).to eq(items.map(&:to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,22 +4,22 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe WhatsNewController do
|
||||
describe 'whats_new_path' do
|
||||
let(:item) { double(:item) }
|
||||
let(:highlights) { double(:highlight, items: [item], map: [item].map, next_page: 2) }
|
||||
|
||||
context 'with whats_new_drawer feature enabled' do
|
||||
before do
|
||||
stub_feature_flags(whats_new_drawer: true)
|
||||
end
|
||||
|
||||
context 'with no page param' do
|
||||
let(:most_recent) { { items: [item], next_page: 2 } }
|
||||
let(:item) { double(:item) }
|
||||
|
||||
it 'responds with paginated data and headers' do
|
||||
allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(most_recent)
|
||||
allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(highlights)
|
||||
allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
|
||||
|
||||
get whats_new_path, xhr: true
|
||||
|
||||
expect(response.body).to eq(most_recent[:items].to_json)
|
||||
expect(response.body).to eq(highlights.items.to_json)
|
||||
expect(response.headers['X-Next-Page']).to eq(2)
|
||||
end
|
||||
end
|
||||
|
|
@ -37,6 +37,18 @@ RSpec.describe WhatsNewController do
|
|||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with version param' do
|
||||
it 'returns items without pagination headers' do
|
||||
allow(ReleaseHighlight).to receive(:for_version).with(version: '42').and_return(highlights)
|
||||
allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
|
||||
|
||||
get whats_new_path(version: 42), xhr: true
|
||||
|
||||
expect(response.body).to eq(highlights.items.to_json)
|
||||
expect(response.headers['X-Next-Page']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with whats_new_drawer feature disabled' do
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Groups::TransferService do
|
||||
let(:user) { create(:user) }
|
||||
let(:new_parent_group) { create(:group, :public) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:new_parent_group) { create(:group, :public) }
|
||||
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
|
||||
let(:transfer_service) { described_class.new(group, user) }
|
||||
|
||||
context 'handling packages' do
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:new_group) { create(:group, :public) }
|
||||
let(:project) { create(:project, :public, namespace: group) }
|
||||
let(:new_group) { create(:group, :public) }
|
||||
|
||||
before do
|
||||
group.add_owner(user)
|
||||
|
|
@ -35,8 +35,8 @@ RSpec.describe Groups::TransferService do
|
|||
it_behaves_like 'transfer not allowed'
|
||||
|
||||
context 'with a project within subgroup' do
|
||||
let(:root_group) { create(:group) }
|
||||
let(:group) { create(:group, parent: root_group) }
|
||||
let_it_be(:root_group) { create(:group) }
|
||||
let_it_be(:group) { create(:group, parent: root_group) }
|
||||
|
||||
before do
|
||||
root_group.add_owner(user)
|
||||
|
|
@ -79,8 +79,6 @@ RSpec.describe Groups::TransferService do
|
|||
|
||||
shared_examples 'ensuring allowed transfer for a group' do
|
||||
context "when there's an exception on GitLab shell directories" do
|
||||
let(:new_parent_group) { create(:group, :public) }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(described_class) do |instance|
|
||||
allow(instance).to receive(:update_group_attributes).and_raise(Gitlab::UpdatePathError, 'namespace directory cannot be moved')
|
||||
|
|
@ -101,7 +99,7 @@ RSpec.describe Groups::TransferService do
|
|||
|
||||
describe '#execute' do
|
||||
context 'when transforming a group into a root group' do
|
||||
let!(:group) { create(:group, :public, :nested) }
|
||||
let_it_be_with_reload(:group) { create(:group, :public, :nested) }
|
||||
|
||||
it_behaves_like 'ensuring allowed transfer for a group'
|
||||
|
||||
|
|
@ -115,7 +113,7 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'when the user does not have the right policies' do
|
||||
let!(:group_member) { create(:group_member, :guest, group: group, user: user) }
|
||||
let_it_be(:group_member) { create(:group_member, :guest, group: group, user: user) }
|
||||
|
||||
it "returns false" do
|
||||
expect(transfer_service.execute(nil)).to be_falsy
|
||||
|
|
@ -128,7 +126,7 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'when there is a group with the same path' do
|
||||
let!(:group) { create(:group, :public, :nested, path: 'not-unique') }
|
||||
let_it_be(:group) { create(:group, :public, :nested, path: 'not-unique') }
|
||||
|
||||
before do
|
||||
create(:group, path: 'not-unique')
|
||||
|
|
@ -145,9 +143,9 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'when the group is a subgroup and the transfer is valid' do
|
||||
let!(:subgroup1) { create(:group, :private, parent: group) }
|
||||
let!(:subgroup2) { create(:group, :internal, parent: group) }
|
||||
let!(:project1) { create(:project, :repository, :private, namespace: group) }
|
||||
let_it_be(:subgroup1) { create(:group, :private, parent: group) }
|
||||
let_it_be(:subgroup2) { create(:group, :internal, parent: group) }
|
||||
let_it_be(:project1) { create(:project, :repository, :private, namespace: group) }
|
||||
|
||||
before do
|
||||
transfer_service.execute(nil)
|
||||
|
|
@ -173,12 +171,12 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'when transferring a subgroup into another group' do
|
||||
let(:group) { create(:group, :public, :nested) }
|
||||
let_it_be_with_reload(:group) { create(:group, :public, :nested) }
|
||||
|
||||
it_behaves_like 'ensuring allowed transfer for a group'
|
||||
|
||||
context 'when the new parent group is the same as the previous parent group' do
|
||||
let(:group) { create(:group, :public, :nested, parent: new_parent_group) }
|
||||
let_it_be(:group) { create(:group, :public, :nested, parent: new_parent_group) }
|
||||
|
||||
it 'returns false' do
|
||||
expect(transfer_service.execute(new_parent_group)).to be_falsy
|
||||
|
|
@ -191,7 +189,7 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'when the user does not have the right policies' do
|
||||
let!(:group_member) { create(:group_member, :guest, group: group, user: user) }
|
||||
let_it_be(:group_member) { create(:group_member, :guest, group: group, user: user) }
|
||||
|
||||
it "returns false" do
|
||||
expect(transfer_service.execute(new_parent_group)).to be_falsy
|
||||
|
|
@ -221,7 +219,7 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'when the parent group has a project with the same path' do
|
||||
let!(:group) { create(:group, :public, :nested, path: 'foo') }
|
||||
let_it_be_with_reload(:group) { create(:group, :public, :nested, path: 'foo') }
|
||||
|
||||
before do
|
||||
create(:group_member, :owner, group: new_parent_group, user: user)
|
||||
|
|
@ -240,8 +238,13 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'when the group is allowed to be transferred' do
|
||||
let_it_be(:new_parent_group_integration) { create(:slack_service, group: new_parent_group, project: nil, webhook: 'http://new-group.slack.com') }
|
||||
|
||||
before do
|
||||
allow(PropagateIntegrationWorker).to receive(:perform_async)
|
||||
|
||||
create(:group_member, :owner, group: new_parent_group, user: user)
|
||||
|
||||
transfer_service.execute(new_parent_group)
|
||||
end
|
||||
|
||||
|
|
@ -267,6 +270,30 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a group integration' do
|
||||
let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
|
||||
let(:new_created_integration) { Service.find_by(group: group) }
|
||||
|
||||
context 'with an inherited integration' do
|
||||
let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com', inherit_from_id: instance_integration.id) }
|
||||
|
||||
it 'replaces inherited integrations', :aggregate_failures do
|
||||
expect(new_created_integration.webhook).to eq(new_parent_group_integration.webhook)
|
||||
expect(PropagateIntegrationWorker).to have_received(:perform_async).with(new_created_integration.id)
|
||||
expect(Service.count).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a custom integration' do
|
||||
let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com') }
|
||||
|
||||
it 'does not updates the integrations', :aggregate_failures do
|
||||
expect { transfer_service.execute(new_parent_group) }.not_to change { group_integration.webhook }
|
||||
expect(PropagateIntegrationWorker).not_to have_received(:perform_async)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates visibility for the group based on the parent group' do
|
||||
expect(group.visibility_level).to eq(new_parent_group.visibility_level)
|
||||
end
|
||||
|
|
@ -464,7 +491,7 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'updated paths' do
|
||||
let(:group) { create(:group, :public) }
|
||||
let_it_be_with_reload(:group) { create(:group, :public) }
|
||||
|
||||
before do
|
||||
transfer_service.execute(new_parent_group)
|
||||
|
|
@ -500,10 +527,10 @@ RSpec.describe Groups::TransferService do
|
|||
end
|
||||
|
||||
context 'resets project authorizations' do
|
||||
let(:old_parent_group) { create(:group) }
|
||||
let(:group) { create(:group, :private, parent: old_parent_group) }
|
||||
let(:new_group_member) { create(:user) }
|
||||
let(:old_group_member) { create(:user) }
|
||||
let_it_be(:old_parent_group) { create(:group) }
|
||||
let_it_be_with_reload(:group) { create(:group, :private, parent: old_parent_group) }
|
||||
let_it_be(:new_group_member) { create(:user) }
|
||||
let_it_be(:old_group_member) { create(:user) }
|
||||
|
||||
before do
|
||||
new_parent_group.add_maintainer(new_group_member)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ RSpec.describe Projects::TransferService do
|
|||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:group_integration) { create(:slack_service, group: group, project: nil, webhook: 'http://group.slack.com') }
|
||||
let(:project) { create(:project, :repository, :legacy_storage, namespace: user.namespace) }
|
||||
|
||||
subject(:execute_transfer) { described_class.new(project, user).execute(group).tap { project.reload } }
|
||||
|
|
@ -117,6 +118,30 @@ RSpec.describe Projects::TransferService do
|
|||
shard_name: project.repository_storage
|
||||
)
|
||||
end
|
||||
|
||||
context 'with a project integration' do
|
||||
let_it_be_with_reload(:project) { create(:project, namespace: user.namespace) }
|
||||
let_it_be(:instance_integration) { create(:slack_service, :instance, webhook: 'http://project.slack.com') }
|
||||
|
||||
context 'with an inherited integration' do
|
||||
let_it_be(:project_integration) { create(:slack_service, project: project, webhook: 'http://project.slack.com', inherit_from_id: instance_integration.id) }
|
||||
|
||||
it 'replaces inherited integrations', :aggregate_failures do
|
||||
execute_transfer
|
||||
|
||||
expect(project.slack_service.webhook).to eq(group_integration.webhook)
|
||||
expect(Service.count).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a custom integration' do
|
||||
let_it_be(:project_integration) { create(:slack_service, project: project, webhook: 'http://project.slack.com') }
|
||||
|
||||
it 'does not updates the integrations' do
|
||||
expect { execute_transfer }.not_to change { project.slack_service.webhook }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when transfer fails' do
|
||||
|
|
@ -527,7 +552,7 @@ RSpec.describe Projects::TransferService do
|
|||
group.add_owner(user)
|
||||
end
|
||||
|
||||
it 'schedules a job when pages are deployed' do
|
||||
it 'schedules a job when pages are deployed' do
|
||||
project.mark_pages_as_deployed
|
||||
|
||||
expect(PagesTransferWorker).to receive(:perform_async)
|
||||
|
|
|
|||
Loading…
Reference in New Issue