Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
10e15ac3c2
commit
c3eeb6a8d6
|
|
@ -211,7 +211,7 @@ ping-appsec-for-sast-findings:
|
|||
- .ping-appsec-for-sast-findings:rules
|
||||
variables:
|
||||
# Project Access Token bot ID for /gitlab-com/gl-security/appsec/sast-custom-rules
|
||||
BOT_USER_ID: 13559989
|
||||
BOT_USER_ID: 14406065
|
||||
needs:
|
||||
- semgrep-appsec-custom-rules
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
TYPENAME_MILESTONE,
|
||||
TYPENAME_USER,
|
||||
} from '~/graphql_shared/constants';
|
||||
import { isGid, convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { isGid, convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import {
|
||||
ListType,
|
||||
MilestoneIDs,
|
||||
|
|
@ -202,6 +202,38 @@ export function moveItemListHelper(item, fromList, toList) {
|
|||
return updatedItem;
|
||||
}
|
||||
|
||||
export function moveItemVariables({
|
||||
iid,
|
||||
epicId,
|
||||
fromListId,
|
||||
toListId,
|
||||
moveBeforeId,
|
||||
moveAfterId,
|
||||
isIssue,
|
||||
boardId,
|
||||
itemToMove,
|
||||
}) {
|
||||
if (isIssue) {
|
||||
return {
|
||||
iid,
|
||||
boardId,
|
||||
projectPath: itemToMove.referencePath.split(/[#]/)[0],
|
||||
moveBeforeId: moveBeforeId ? getIdFromGraphQLId(moveBeforeId) : undefined,
|
||||
moveAfterId: moveAfterId ? getIdFromGraphQLId(moveAfterId) : undefined,
|
||||
fromListId: getIdFromGraphQLId(fromListId),
|
||||
toListId: getIdFromGraphQLId(toListId),
|
||||
};
|
||||
}
|
||||
return {
|
||||
epicId,
|
||||
boardId,
|
||||
moveBeforeId,
|
||||
moveAfterId,
|
||||
fromListId,
|
||||
toListId,
|
||||
};
|
||||
}
|
||||
|
||||
export function isListDraggable(list) {
|
||||
return list.listType !== ListType.backlog && list.listType !== ListType.closed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { sortableStart, sortableEnd } from '~/sortable/utils';
|
|||
import Tracking from '~/tracking';
|
||||
import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
|
||||
import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import {
|
||||
DEFAULT_BOARD_LIST_ITEMS_SIZE,
|
||||
toggleFormEventPrefix,
|
||||
|
|
@ -16,6 +17,13 @@ import {
|
|||
listIssuablesQueries,
|
||||
ListType,
|
||||
} from 'ee_else_ce/boards/constants';
|
||||
import {
|
||||
addItemToList,
|
||||
removeItemFromList,
|
||||
updateEpicsCount,
|
||||
updateIssueCountAndWeight,
|
||||
} from '../graphql/cache_updates';
|
||||
import { shouldCloneCard, moveItemVariables } from '../boards_util';
|
||||
import eventHub from '../eventhub';
|
||||
import BoardCard from './board_card.vue';
|
||||
import BoardNewIssue from './board_new_issue.vue';
|
||||
|
|
@ -37,7 +45,7 @@ export default {
|
|||
GlIntersectionObserver,
|
||||
BoardCardMoveToPosition,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
mixins: [Tracking.mixin(), glFeatureFlagMixin()],
|
||||
inject: [
|
||||
'isEpicBoard',
|
||||
'isGroupBoard',
|
||||
|
|
@ -73,6 +81,8 @@ export default {
|
|||
showEpicForm: false,
|
||||
currentList: null,
|
||||
isLoadingMore: false,
|
||||
toListId: null,
|
||||
toList: {},
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
|
@ -111,6 +121,29 @@ export default {
|
|||
isSingleRequest: true,
|
||||
},
|
||||
},
|
||||
toList: {
|
||||
query() {
|
||||
return listIssuablesQueries[this.issuableType].query;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
id: this.toListId,
|
||||
...this.listQueryVariables,
|
||||
};
|
||||
},
|
||||
skip() {
|
||||
return !this.toListId;
|
||||
},
|
||||
update(data) {
|
||||
return data[this.boardType].board.lists.nodes[0];
|
||||
},
|
||||
context: {
|
||||
isSingleRequest: true,
|
||||
},
|
||||
error() {
|
||||
// handle error
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['pageInfoByListId', 'listsFlags', 'isUpdateIssueOrderInProgress']),
|
||||
|
|
@ -205,6 +238,9 @@ export default {
|
|||
showMoveToPosition() {
|
||||
return !this.disabled && this.list.listType !== ListType.closed;
|
||||
},
|
||||
shouldCloneCard() {
|
||||
return shouldCloneCard(this.list.listType, this.toList.listType);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
boardListItems() {
|
||||
|
|
@ -337,15 +373,123 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
this.moveItem({
|
||||
itemId,
|
||||
itemIid,
|
||||
itemPath,
|
||||
fromListId: from.dataset.listId,
|
||||
toListId: to.dataset.listId,
|
||||
moveBeforeId,
|
||||
moveAfterId,
|
||||
if (this.isApolloBoard) {
|
||||
this.moveBoardItem(
|
||||
{
|
||||
epicId: itemId,
|
||||
iid: itemIid,
|
||||
fromListId: from.dataset.listId,
|
||||
toListId: to.dataset.listId,
|
||||
moveBeforeId,
|
||||
moveAfterId,
|
||||
},
|
||||
newIndex,
|
||||
);
|
||||
} else {
|
||||
this.moveItem({
|
||||
itemId,
|
||||
itemIid,
|
||||
itemPath,
|
||||
fromListId: from.dataset.listId,
|
||||
toListId: to.dataset.listId,
|
||||
moveBeforeId,
|
||||
moveAfterId,
|
||||
});
|
||||
}
|
||||
},
|
||||
isItemInTheList(itemIid) {
|
||||
const items = this.toList?.[`${this.issuableType}s`]?.nodes || [];
|
||||
return items.some((item) => item.iid === itemIid);
|
||||
},
|
||||
async moveBoardItem(variables, newIndex) {
|
||||
const { fromListId, toListId, iid } = variables;
|
||||
this.toListId = toListId;
|
||||
await this.$nextTick(); // we need this next tick to retrieve `toList` from Apollo cache
|
||||
|
||||
const itemToMove = this.boardListItems.find((item) => item.iid === iid);
|
||||
|
||||
if (this.shouldCloneCard && this.isItemInTheList(iid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: listIssuablesQueries[this.issuableType].moveMutation,
|
||||
variables: {
|
||||
...moveItemVariables({
|
||||
...variables,
|
||||
isIssue: !this.isEpicBoard,
|
||||
boardId: this.boardId,
|
||||
itemToMove,
|
||||
}),
|
||||
withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight,
|
||||
},
|
||||
update: (cache, { data: { issuableMoveList } }) =>
|
||||
this.updateCacheAfterMovingItem({
|
||||
issuableMoveList,
|
||||
fromListId,
|
||||
toListId,
|
||||
newIndex,
|
||||
cache,
|
||||
}),
|
||||
optimisticResponse: {
|
||||
issuableMoveList: {
|
||||
issuable: itemToMove,
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
// handle error
|
||||
}
|
||||
},
|
||||
updateCacheAfterMovingItem({ issuableMoveList, fromListId, toListId, newIndex, cache }) {
|
||||
const { issuable } = issuableMoveList;
|
||||
if (!this.shouldCloneCard) {
|
||||
removeItemFromList({
|
||||
query: listIssuablesQueries[this.issuableType].query,
|
||||
variables: { ...this.listQueryVariables, id: fromListId },
|
||||
boardType: this.boardType,
|
||||
id: issuable.id,
|
||||
issuableType: this.issuableType,
|
||||
cache,
|
||||
});
|
||||
}
|
||||
|
||||
addItemToList({
|
||||
query: listIssuablesQueries[this.issuableType].query,
|
||||
variables: { ...this.listQueryVariables, id: toListId },
|
||||
issuable,
|
||||
newIndex,
|
||||
boardType: this.boardType,
|
||||
issuableType: this.issuableType,
|
||||
cache,
|
||||
});
|
||||
|
||||
this.updateCountAndWeight({ fromListId, toListId, issuable, cache });
|
||||
},
|
||||
updateCountAndWeight({ fromListId, toListId, issuable, isAddingIssue, cache }) {
|
||||
if (!this.isEpicBoard) {
|
||||
updateIssueCountAndWeight({
|
||||
fromListId,
|
||||
toListId,
|
||||
filterParams: this.filterParams,
|
||||
issuable,
|
||||
shouldClone: isAddingIssue || this.shouldCloneCard,
|
||||
cache,
|
||||
});
|
||||
} else {
|
||||
const { issuableType, filterParams } = this;
|
||||
updateEpicsCount({
|
||||
issuableType,
|
||||
toListId,
|
||||
fromListId,
|
||||
filterParams,
|
||||
issuable,
|
||||
shouldClone: this.shouldCloneCard,
|
||||
cache,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ import updateBoardListMutation from './graphql/board_list_update.mutation.graphq
|
|||
import toggleListCollapsedMutation from './graphql/client/board_toggle_collapsed.mutation.graphql';
|
||||
import issueSetSubscriptionMutation from './graphql/issue_set_subscription.mutation.graphql';
|
||||
import issueSetTitleMutation from './graphql/issue_set_title.mutation.graphql';
|
||||
import issueMoveListMutation from './graphql/issue_move_list.mutation.graphql';
|
||||
import groupBoardQuery from './graphql/group_board.query.graphql';
|
||||
import projectBoardQuery from './graphql/project_board.query.graphql';
|
||||
import listIssuesQuery from './graphql/lists_issues.query.graphql';
|
||||
import listDeferredQuery from './graphql/board_lists_deferred.query.graphql';
|
||||
|
||||
export const BoardType = {
|
||||
project: 'project',
|
||||
|
|
@ -72,6 +74,12 @@ export const listsQuery = {
|
|||
},
|
||||
};
|
||||
|
||||
export const listsDeferredQuery = {
|
||||
[TYPE_ISSUE]: {
|
||||
query: listDeferredQuery,
|
||||
},
|
||||
};
|
||||
|
||||
export const createListMutations = {
|
||||
[TYPE_ISSUE]: {
|
||||
mutation: createBoardListMutation,
|
||||
|
|
@ -117,6 +125,7 @@ export const subscriptionQueries = {
|
|||
export const listIssuablesQueries = {
|
||||
[TYPE_ISSUE]: {
|
||||
query: listIssuesQuery,
|
||||
moveMutation: issueMoveListMutation,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
import produce from 'immer';
|
||||
import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
|
||||
import { listsDeferredQuery } from 'ee_else_ce/boards/constants';
|
||||
|
||||
export function removeItemFromList({ query, variables, boardType, id, issuableType, cache }) {
|
||||
cache.updateQuery({ query, variables }, (sourceData) =>
|
||||
produce(sourceData, (draftData) => {
|
||||
const { nodes: items } = draftData[boardType].board.lists.nodes[0][`${issuableType}s`];
|
||||
items.splice(
|
||||
items.findIndex((item) => item.id === id),
|
||||
1,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function addItemToList({
|
||||
query,
|
||||
variables,
|
||||
boardType,
|
||||
issuable,
|
||||
newIndex,
|
||||
issuableType,
|
||||
cache,
|
||||
}) {
|
||||
cache.updateQuery({ query, variables }, (sourceData) =>
|
||||
produce(sourceData, (draftData) => {
|
||||
const { nodes: items } = draftData[boardType].board.lists.nodes[0][`${issuableType}s`];
|
||||
items.splice(newIndex, 0, issuable);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function updateIssueCountAndWeight({
|
||||
fromListId,
|
||||
toListId,
|
||||
filterParams,
|
||||
issuable: issue,
|
||||
shouldClone,
|
||||
cache,
|
||||
}) {
|
||||
if (!shouldClone) {
|
||||
cache.updateQuery(
|
||||
{
|
||||
query: listQuery,
|
||||
variables: { id: fromListId, filters: filterParams },
|
||||
},
|
||||
({ boardList }) => ({
|
||||
boardList: {
|
||||
...boardList,
|
||||
issuesCount: boardList.issuesCount - 1,
|
||||
totalWeight: boardList.totalWeight - issue.weight,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
cache.updateQuery(
|
||||
{
|
||||
query: listQuery,
|
||||
variables: { id: toListId, filters: filterParams },
|
||||
},
|
||||
({ boardList }) => ({
|
||||
boardList: {
|
||||
...boardList,
|
||||
issuesCount: boardList.issuesCount + 1,
|
||||
totalWeight: boardList.totalWeight + issue.weight,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function updateEpicsCount({
|
||||
issuableType,
|
||||
filterParams,
|
||||
fromListId,
|
||||
toListId,
|
||||
issuable: epic,
|
||||
shouldClone,
|
||||
cache,
|
||||
}) {
|
||||
const epicWeight = epic.descendantWeightSum.openedIssues + epic.descendantWeightSum.closedIssues;
|
||||
if (!shouldClone) {
|
||||
cache.updateQuery(
|
||||
{
|
||||
query: listsDeferredQuery[issuableType].query,
|
||||
variables: { id: fromListId, filters: filterParams },
|
||||
},
|
||||
({ epicBoardList }) => ({
|
||||
epicBoardList: {
|
||||
...epicBoardList,
|
||||
metadata: {
|
||||
epicsCount: epicBoardList.metadata.epicsCount - 1,
|
||||
totalWeight: epicBoardList.metadata.totalWeight - epicWeight,
|
||||
...epicBoardList.metadata,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
cache.updateQuery(
|
||||
{
|
||||
query: listsDeferredQuery[issuableType].query,
|
||||
variables: { id: toListId, filters: filterParams },
|
||||
},
|
||||
({ epicBoardList }) => ({
|
||||
epicBoardList: {
|
||||
...epicBoardList,
|
||||
metadata: {
|
||||
epicsCount: epicBoardList.metadata.epicsCount + 1,
|
||||
totalWeight: epicBoardList.metadata.totalWeight + epicWeight,
|
||||
...epicBoardList.metadata,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ mutation issueMoveList(
|
|||
$moveBeforeId: ID
|
||||
$moveAfterId: ID
|
||||
) {
|
||||
issueMoveList(
|
||||
issuableMoveList: issueMoveList(
|
||||
input: {
|
||||
projectPath: $projectPath
|
||||
iid: $iid
|
||||
|
|
@ -20,7 +20,7 @@ mutation issueMoveList(
|
|||
moveAfterId: $moveAfterId
|
||||
}
|
||||
) {
|
||||
issue {
|
||||
issuable: issue {
|
||||
...Issue
|
||||
}
|
||||
errors
|
||||
|
|
|
|||
|
|
@ -602,8 +602,8 @@ export default {
|
|||
cache,
|
||||
{
|
||||
data: {
|
||||
issueMoveList: {
|
||||
issue: { weight },
|
||||
issuableMoveList: {
|
||||
issuable: { weight },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -661,11 +661,11 @@ export default {
|
|||
},
|
||||
});
|
||||
|
||||
if (data?.issueMoveList?.errors.length || !data.issueMoveList) {
|
||||
if (data?.issuableMoveList?.errors.length || !data.issuableMoveList) {
|
||||
throw new Error('issueMoveList empty');
|
||||
}
|
||||
|
||||
commit(types.MUTATE_ISSUE_SUCCESS, { issue: data.issueMoveList.issue });
|
||||
commit(types.MUTATE_ISSUE_SUCCESS, { issue: data.issuableMoveList.issuable });
|
||||
commit(types.MUTATE_ISSUE_IN_PROGRESS, false);
|
||||
} catch {
|
||||
commit(types.MUTATE_ISSUE_IN_PROGRESS, false);
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
* Used in the environments table.
|
||||
*/
|
||||
|
||||
import { GlDropdownItem, GlModalDirective } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdownItem, GlModalDirective } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
import setEnvironmentToDelete from '../graphql/mutations/set_environment_to_delete.mutation.graphql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdownItem,
|
||||
GlDisclosureDropdownItem,
|
||||
},
|
||||
directives: {
|
||||
GlModalDirective,
|
||||
|
|
@ -30,11 +30,15 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
item: {
|
||||
text: s__('Environments|Delete environment'),
|
||||
extraAttrs: {
|
||||
variant: 'danger',
|
||||
class: 'gl-text-red-500!',
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
i18n: {
|
||||
title: s__('Environments|Delete environment'),
|
||||
},
|
||||
mounted() {
|
||||
if (!this.graphql) {
|
||||
eventHub.$on('deleteEnvironment', this.onDeleteEnvironment);
|
||||
|
|
@ -65,12 +69,10 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-dropdown-item
|
||||
<gl-disclosure-dropdown-item
|
||||
v-gl-modal-directive.delete-environment-modal
|
||||
:item="item"
|
||||
:loading="isLoading"
|
||||
variant="danger"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ $options.i18n.title }}
|
||||
</gl-dropdown-item>
|
||||
@action="onClick"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
* Renders a prevent auto-stop button.
|
||||
* Used in environments table.
|
||||
*/
|
||||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdownItem } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
import cancelAutoStopMutation from '../graphql/mutations/cancel_auto_stop.mutation.graphql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdownItem,
|
||||
GlDisclosureDropdownItem,
|
||||
},
|
||||
props: {
|
||||
autoStopUrl: {
|
||||
|
|
@ -23,6 +23,11 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
item: { text: __('Prevent auto-stopping') },
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onPinClick() {
|
||||
if (this.graphql) {
|
||||
|
|
@ -35,11 +40,8 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
title: __('Prevent auto-stopping'),
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-dropdown-item @click="onPinClick">
|
||||
{{ $options.title }}
|
||||
</gl-dropdown-item>
|
||||
<gl-disclosure-dropdown-item :item="item" @action="onPinClick" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@
|
|||
*
|
||||
* Makes a post request when the button is clicked.
|
||||
*/
|
||||
import { GlModalDirective, GlDropdownItem } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdownItem, GlModalDirective } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
import setEnvironmentToRollback from '../graphql/mutations/set_environment_to_rollback.mutation.graphql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdownItem,
|
||||
GlDisclosureDropdownItem,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
|
|
@ -41,12 +41,14 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
title() {
|
||||
return this.isLastDeployment
|
||||
? s__('Environments|Re-deploy to environment')
|
||||
: s__('Environments|Rollback environment');
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
item: {
|
||||
text: this.isLastDeployment
|
||||
? s__('Environments|Re-deploy to environment')
|
||||
: s__('Environments|Rollback environment'),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -71,7 +73,5 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-dropdown-item v-gl-modal.confirm-rollback-modal @click="onClick">
|
||||
{{ title }}
|
||||
</gl-dropdown-item>
|
||||
<gl-disclosure-dropdown-item v-gl-modal.confirm-rollback-modal :item="item" @action="onClick" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
* Renders a terminal button to open a web terminal.
|
||||
* Used in environments table.
|
||||
*/
|
||||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdownItem } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdownItem,
|
||||
GlDisclosureDropdownItem,
|
||||
},
|
||||
props: {
|
||||
terminalPath: {
|
||||
|
|
@ -22,11 +22,13 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
},
|
||||
title: __('Terminal'),
|
||||
data() {
|
||||
return {
|
||||
item: { text: __('Terminal'), href: this.terminalPath },
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-dropdown-item :href="terminalPath" :disabled="disabled">
|
||||
{{ $options.title }}
|
||||
</gl-dropdown-item>
|
||||
<gl-disclosure-dropdown-item :item="item" :disabled="disabled" />
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import {
|
||||
GlCollapse,
|
||||
GlDropdown,
|
||||
GlBadge,
|
||||
GlButton,
|
||||
GlCollapse,
|
||||
GlDisclosureDropdown,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
GlTooltipDirective as GlTooltip,
|
||||
|
|
@ -27,8 +27,8 @@ import KubernetesOverview from './kubernetes_overview.vue';
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlDisclosureDropdown,
|
||||
GlCollapse,
|
||||
GlDropdown,
|
||||
GlBadge,
|
||||
GlButton,
|
||||
GlLink,
|
||||
|
|
@ -284,14 +284,14 @@ export default {
|
|||
graphql
|
||||
/>
|
||||
|
||||
<gl-dropdown
|
||||
<gl-disclosure-dropdown
|
||||
v-if="hasExtraActions"
|
||||
icon="ellipsis_v"
|
||||
text-sr-only
|
||||
:text="__('More actions')"
|
||||
category="secondary"
|
||||
no-caret
|
||||
right
|
||||
icon="ellipsis_v"
|
||||
category="secondary"
|
||||
placement="right"
|
||||
:toggle-text="__('More actions')"
|
||||
>
|
||||
<rollback
|
||||
v-if="retryPath"
|
||||
|
|
@ -325,7 +325,7 @@ export default {
|
|||
data-track-label="environment_delete"
|
||||
graphql
|
||||
/>
|
||||
</gl-dropdown>
|
||||
</gl-disclosure-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem, GlLink } from '@gitlab/ui';
|
||||
import { GlCollapsibleListbox, GlLink } from '@gitlab/ui';
|
||||
import { mapState } from 'vuex';
|
||||
import { s__ } from '~/locale';
|
||||
import { defaultIntegrationLevel, overrideDropdownDescriptions } from '~/integrations/constants';
|
||||
|
||||
const dropdownOptions = [
|
||||
{
|
||||
value: false,
|
||||
value: 'default',
|
||||
text: s__('Integrations|Use default settings'),
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
value: 'custom',
|
||||
text: s__('Integrations|Use custom settings'),
|
||||
},
|
||||
];
|
||||
|
|
@ -19,8 +19,7 @@ export default {
|
|||
dropdownOptions,
|
||||
name: 'OverrideDropdown',
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlCollapsibleListbox,
|
||||
GlLink,
|
||||
},
|
||||
props: {
|
||||
|
|
@ -39,8 +38,10 @@ export default {
|
|||
},
|
||||
},
|
||||
data() {
|
||||
const selectedValue = this.override ? 'custom' : 'default';
|
||||
return {
|
||||
selected: dropdownOptions.find((x) => x.value === this.override),
|
||||
selectedValue,
|
||||
selectedOption: dropdownOptions.find((x) => x.value === selectedValue),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -54,9 +55,10 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
onClick(option) {
|
||||
this.selected = option;
|
||||
this.$emit('change', option.value);
|
||||
onSelect(value) {
|
||||
this.selectedValue = value;
|
||||
this.selectedOption = dropdownOptions.find((item) => item.value === value);
|
||||
this.$emit('change', value === 'custom');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -73,14 +75,11 @@ export default {
|
|||
}}</gl-link>
|
||||
</span>
|
||||
<input name="service[inherit_from_id]" :value="override ? '' : inheritFromId" type="hidden" />
|
||||
<gl-dropdown :text="selected.text">
|
||||
<gl-dropdown-item
|
||||
v-for="option in $options.dropdownOptions"
|
||||
:key="option.value"
|
||||
@click="onClick(option)"
|
||||
>
|
||||
{{ option.text }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
<gl-collapsible-listbox
|
||||
v-model="selectedValue"
|
||||
:toggle-text="selectedOption.text"
|
||||
:items="$options.dropdownOptions"
|
||||
@select="onSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const PERSISTENT_USER_CALLOUTS = [
|
|||
'.js-unlimited-members-during-trial-alert',
|
||||
'.js-branch-rules-info-callout',
|
||||
'.js-new-navigation-callout',
|
||||
'.js-code-suggestions-third-party-callout',
|
||||
];
|
||||
|
||||
const initCallouts = () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# TODO: Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/402699
|
||||
module Issues
|
||||
module ForbidIssueTypeColumnUsage
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ForbiddenColumnUsed = Class.new(StandardError)
|
||||
|
||||
included do
|
||||
WorkItems::Type.base_types.each do |base_type, _value|
|
||||
define_method "#{base_type}?".to_sym do
|
||||
error_message = <<~ERROR
|
||||
`#{model_name.element}.#{base_type}?` uses the `issue_type` column underneath. As we want to remove the column,
|
||||
its usage is forbidden. You should use the `work_item_types` table instead.
|
||||
|
||||
# Before
|
||||
|
||||
#{model_name.element}.#{base_type}? => true
|
||||
|
||||
# After
|
||||
|
||||
#{model_name.element}.work_item_type.#{base_type}? => true
|
||||
|
||||
More details in https://gitlab.com/groups/gitlab-org/-/epics/10529
|
||||
ERROR
|
||||
|
||||
raise ForbiddenColumnUsed, error_message
|
||||
end
|
||||
|
||||
define_singleton_method base_type.to_sym do
|
||||
error = ForbiddenColumnUsed.new(
|
||||
<<~ERROR
|
||||
`#{name}.#{base_type}` uses the `issue_type` column underneath. As we want to remove the column,
|
||||
its usage is forbidden. You should use the `work_item_types` table instead.
|
||||
|
||||
# Before
|
||||
|
||||
#{name}.#{base_type}
|
||||
|
||||
# After
|
||||
|
||||
#{name}.with_issue_type(:#{base_type})
|
||||
|
||||
More details in https://gitlab.com/groups/gitlab-org/-/epics/10529
|
||||
ERROR
|
||||
)
|
||||
|
||||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(
|
||||
error,
|
||||
method_name: "#{name}.#{base_type}"
|
||||
)
|
||||
|
||||
with_issue_type(base_type.to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -21,7 +21,7 @@ class Integration < ApplicationRecord
|
|||
asana assembla bamboo bugzilla buildkite campfire clickup confluence custom_issue_tracker datadog discord
|
||||
drone_ci emails_on_push ewm external_wiki hangouts_chat harbor irker jira
|
||||
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
|
||||
pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands squash_tm teamcity
|
||||
pivotaltracker prometheus pumble pushover redmine slack slack_slash_commands squash_tm teamcity telegram
|
||||
unify_circuit webex_teams youtrack zentao
|
||||
].freeze
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ class Integration < ApplicationRecord
|
|||
|
||||
def self.project_specific_integration_names
|
||||
names = PROJECT_SPECIFIC_INTEGRATION_NAMES.dup
|
||||
names.delete('gitlab_slack_application') unless Gitlab::CurrentSettings.slack_app_enabled || Rails.env.test?
|
||||
names.delete('gitlab_slack_application') unless Gitlab::CurrentSettings.slack_app_enabled || Gitlab.dev_or_test_env?
|
||||
names
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Integrations
|
||||
class Telegram < BaseChatNotification
|
||||
TELEGRAM_HOSTNAME = "https://api.telegram.org/bot%{token}/sendMessage"
|
||||
|
||||
field :token,
|
||||
section: SECTION_TYPE_CONNECTION,
|
||||
help: -> { s_('TelegramIntegration|Unique authentication token.') },
|
||||
non_empty_password_title: -> { s_('TelegramIntegration|New token') },
|
||||
non_empty_password_help: -> { s_('TelegramIntegration|Leave blank to use your current token.') },
|
||||
placeholder: '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11',
|
||||
exposes_secrets: true,
|
||||
is_secret: true,
|
||||
required: true
|
||||
|
||||
field :room,
|
||||
title: 'Channel identifier',
|
||||
section: SECTION_TYPE_CONFIGURATION,
|
||||
help: "Unique identifier for the target chat or the username of the target channel (format: @channelusername)",
|
||||
placeholder: '@channelusername',
|
||||
required: true
|
||||
|
||||
with_options if: :activated? do
|
||||
validates :token, :room, presence: true
|
||||
end
|
||||
|
||||
before_validation :set_webhook
|
||||
|
||||
def title
|
||||
'Telegram'
|
||||
end
|
||||
|
||||
def description
|
||||
s_("TelegramIntegration|Send notifications about project events to Telegram.")
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
'telegram'
|
||||
end
|
||||
|
||||
def help
|
||||
docs_link = ActionController::Base.helpers.link_to(
|
||||
_('Learn more.'),
|
||||
Rails.application.routes.url_helpers.help_page_url('user/project/integrations/telegram'),
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
)
|
||||
format(s_("TelegramIntegration|Send notifications about project events to Telegram. %{docs_link}"),
|
||||
docs_link: docs_link.html_safe
|
||||
)
|
||||
end
|
||||
|
||||
def fields
|
||||
self.class.fields + build_event_channels
|
||||
end
|
||||
|
||||
def self.supported_events
|
||||
super - ['deployment']
|
||||
end
|
||||
|
||||
def sections
|
||||
[
|
||||
{
|
||||
type: SECTION_TYPE_CONNECTION,
|
||||
title: s_('Integrations|Connection details'),
|
||||
description: help
|
||||
},
|
||||
{
|
||||
type: SECTION_TYPE_TRIGGER,
|
||||
title: s_('Integrations|Trigger'),
|
||||
description: s_('Integrations|An event will be triggered when one of the following items happen.')
|
||||
},
|
||||
{
|
||||
type: SECTION_TYPE_CONFIGURATION,
|
||||
title: s_('Integrations|Notification settings'),
|
||||
description: s_('Integrations|Configure the scope of notifications.')
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_webhook
|
||||
self.webhook = format(TELEGRAM_HOSTNAME, token: token) if token.present?
|
||||
end
|
||||
|
||||
def notify(message, _opts)
|
||||
body = {
|
||||
text: message.summary,
|
||||
chat_id: room,
|
||||
parse_mode: 'markdown'
|
||||
}
|
||||
|
||||
header = { 'Content-Type' => 'application/json' }
|
||||
response = Gitlab::HTTP.post(webhook, headers: header, body: Gitlab::Json.dump(body))
|
||||
|
||||
response if response.success?
|
||||
end
|
||||
|
||||
def custom_data(data)
|
||||
super(data).merge(markdown: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -39,7 +39,6 @@ class Issue < ApplicationRecord
|
|||
DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze
|
||||
|
||||
IssueTypeOutOfSyncError = Class.new(StandardError)
|
||||
ForbiddenColumnUsed = Class.new(StandardError)
|
||||
|
||||
SORTING_PREFERENCE_FIELD = :issues_sort
|
||||
MAX_BRANCH_TEMPLATE = 255
|
||||
|
|
@ -138,28 +137,8 @@ class Issue < ApplicationRecord
|
|||
validate :issue_type_attribute_present
|
||||
|
||||
enum issue_type: WorkItems::Type.base_types
|
||||
|
||||
# TODO: Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/402699
|
||||
WorkItems::Type.base_types.each do |base_type, _value|
|
||||
define_method "#{base_type}?".to_sym do
|
||||
error_message = <<~ERROR
|
||||
`#{base_type}?` uses the `issue_type` column underneath. As we want to remove the column,
|
||||
its usage is forbidden. You should use the `work_item_types` table instead.
|
||||
|
||||
# Before
|
||||
|
||||
issue.requirement? => true
|
||||
|
||||
# After
|
||||
|
||||
issue.work_item_type.requirement? => true
|
||||
|
||||
More details in https://gitlab.com/groups/gitlab-org/-/epics/10529
|
||||
ERROR
|
||||
|
||||
raise ForbiddenColumnUsed, error_message
|
||||
end
|
||||
end
|
||||
include ::Issues::ForbidIssueTypeColumnUsage
|
||||
|
||||
alias_method :issuing_parent, :project
|
||||
alias_attribute :issuing_parent_id, :project_id
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ class Namespace < ApplicationRecord
|
|||
# This should _not_ be `inverse_of: :namespace`, because that would also set
|
||||
# `user.namespace` when this user creates a group with themselves as `owner`.
|
||||
belongs_to :owner, class_name: 'User'
|
||||
belongs_to :organization, class_name: 'Organizations::Organization'
|
||||
|
||||
belongs_to :parent, class_name: "Namespace"
|
||||
has_many :children, -> { where(type: Group.sti_name) }, class_name: "Namespace", foreign_key: :parent_id
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ module Organizations
|
|||
|
||||
before_destroy :check_if_default_organization
|
||||
|
||||
has_many :namespaces
|
||||
has_many :groups
|
||||
|
||||
validates :name,
|
||||
presence: true,
|
||||
length: { maximum: 255 }
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ class Project < ApplicationRecord
|
|||
has_one :slack_slash_commands_integration, class_name: 'Integrations::SlackSlashCommands'
|
||||
has_one :squash_tm_integration, class_name: 'Integrations::SquashTm'
|
||||
has_one :teamcity_integration, class_name: 'Integrations::Teamcity'
|
||||
has_one :telegram_integration, class_name: 'Integrations::Telegram'
|
||||
has_one :unify_circuit_integration, class_name: 'Integrations::UnifyCircuit'
|
||||
has_one :webex_teams_integration, class_name: 'Integrations::WebexTeams'
|
||||
has_one :youtrack_integration, class_name: 'Integrations::Youtrack'
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ module Users
|
|||
repository_storage_limit_banner_warning_threshold: 68, # EE-only
|
||||
repository_storage_limit_banner_alert_threshold: 69, # EE-only
|
||||
repository_storage_limit_banner_error_threshold: 70, # EE-only
|
||||
new_navigation_callout: 71
|
||||
new_navigation_callout: 71,
|
||||
code_suggestions_third_party_callout: 72 # EE-only
|
||||
}
|
||||
|
||||
validates :feature_name,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
= f.text_field :username, name: :username, autocomplete: :username, class: 'form-control gl-form-input', title: _('This field is required.'), autofocus: 'autofocus', required: true
|
||||
.form-group
|
||||
= f.label :password, _('Password')
|
||||
= f.password_field :password, name: :password, autocomplete: :current_password, class: 'form-control gl-form-input js-password', data: { id: 'password', name: 'password' }
|
||||
= f.text_field :vue_password_placeholder, class: 'form-control gl-form-input js-password', data: { id: "#{:crowd}_password", name: 'password' }
|
||||
|
||||
- if render_remember_me
|
||||
= f.gitlab_ui_checkbox_component :remember_me, _('Remember me'), checkbox_options: { name: :remember_me, autocomplete: 'off' }
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
= gitlab_ui_form_for(provider, url: omniauth_callback_path(:user, provider), html: { class: 'gl-p-5 gl-show-field-errors', aria: { live: 'assertive' }, data: { testid: 'new_ldap_user' }}) do |f|
|
||||
.form-group
|
||||
= f.label :username, _('Username')
|
||||
= f.text_field :username, name: :username, autocomplete: :username, class: 'form-control gl-form-input top', title: _('This field is required.'), autofocus: 'autofocus', data: { qa_selector: 'username_field' }, required: true
|
||||
= f.text_field :username, name: :username, autocomplete: :username, class: 'form-control gl-form-input', title: _('This field is required.'), autofocus: 'autofocus', data: { qa_selector: 'username_field' }, required: true
|
||||
.form-group
|
||||
= f.label :password, _('Password')
|
||||
= f.password_field :password, name: :password, autocomplete: :current_password, class: 'form-control gl-form-input js-password', data: { id: "#{provider}-password", name: 'password', qa_selector: 'password_field' }
|
||||
= f.text_field :vue_password_placeholder, class: 'form-control gl-form-input js-password', data: { id: "#{provider}_password", name: 'password', qa_selector: 'password_field' }
|
||||
|
||||
- if render_remember_me
|
||||
= f.gitlab_ui_checkbox_component :remember_me, _('Remember me'), checkbox_options: { name: :remember_me, autocomplete: 'off' }
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
.mobile-overlay
|
||||
= dispensable_render_if_exists 'layouts/header/verification_reminder'
|
||||
.alert-wrapper.gl-force-block-formatting-context
|
||||
= yield :code_suggestions_third_party_alert
|
||||
= dispensable_render 'shared/new_nav_announcement'
|
||||
= dispensable_render 'shared/outdated_browser'
|
||||
= dispensable_render_if_exists "layouts/header/licensed_user_count_threshold"
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
= dispensable_render_if_exists "shared/web_hooks/group_web_hook_disabled_alert"
|
||||
= dispensable_render_if_exists "shared/code_suggestions_alert"
|
||||
= dispensable_render_if_exists "shared/code_suggestions_third_party_alert", source: @group
|
||||
= dispensable_render_if_exists "shared/free_user_cap_alert", source: @group
|
||||
= dispensable_render_if_exists "shared/unlimited_members_during_trial_alert", resource: @group
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
= dispensable_render_if_exists "shared/web_hooks/web_hook_disabled_alert"
|
||||
= dispensable_render_if_exists "projects/code_suggestions_alert", project: @project
|
||||
= dispensable_render_if_exists "projects/code_suggestions_third_party_alert", project: @project
|
||||
= dispensable_render_if_exists "projects/free_user_cap_alert", project: @project
|
||||
= dispensable_render_if_exists 'shared/unlimited_members_during_trial_alert', resource: @project
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: ci_include_components
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109154
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/39064
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/390646
|
||||
milestone: '15.9'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
|
|
|
|||
|
|
@ -247,10 +247,6 @@ ml_candidates:
|
|||
- table: ci_builds
|
||||
column: ci_build_id
|
||||
on_delete: async_nullify
|
||||
namespaces:
|
||||
- table: organizations
|
||||
column: organization_id
|
||||
on_delete: async_nullify
|
||||
p_ci_builds_metadata:
|
||||
- table: projects
|
||||
column: project_id
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.projects_telegram_active
|
||||
description: Count of projects with active integrations for Telegram
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "16.1"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122879
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.projects_inheriting_telegram_active
|
||||
description: Count of active projects inheriting integrations for Telegram
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "16.1"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122879
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.instances_telegram_active
|
||||
description: Count of active instance-level integrations for Telegram
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "16.1"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122879
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.groups_telegram_active
|
||||
description: Count of groups with active integrations for Telegram
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "16.1"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122879
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
key_path: counts.groups_inheriting_telegram_active
|
||||
description: Count of active groups inheriting integrations for Telegram
|
||||
product_section: dev
|
||||
product_stage: manage
|
||||
product_group: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "16.1"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122879
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
performance_indicator_type: []
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -50,6 +50,7 @@ classes:
|
|||
- Integrations::SlackSlashCommands
|
||||
- Integrations::SquashTm
|
||||
- Integrations::Teamcity
|
||||
- Integrations::Telegram
|
||||
- Integrations::UnifyCircuit
|
||||
- Integrations::WebexTeams
|
||||
- Integrations::Youtrack
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddOrganizationIdToNamespace < Gitlab::Database::Migration[2.1]
|
||||
DEFAULT_ORGANIZATION_ID = 1
|
||||
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :namespaces, :organization_id, :bigint, default: DEFAULT_ORGANIZATION_ID, null: true # rubocop:disable Migration/AddColumnsToWideTables
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TrackOrganizationRecordChanges < Gitlab::Database::Migration[2.1]
|
||||
include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers
|
||||
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
track_record_deletions(:organizations)
|
||||
# no-op
|
||||
end
|
||||
|
||||
def down
|
||||
untrack_record_deletions(:organizations)
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PrepareIndexForOrgIdOnNamespaces < Gitlab::Database::Migration[2.1]
|
||||
INDEX_NAME = 'index_namespaces_on_organization_id'
|
||||
|
||||
def up
|
||||
prepare_async_index :namespaces, :organization_id, name: INDEX_NAME
|
||||
# no-op
|
||||
end
|
||||
|
||||
def down
|
||||
unprepare_async_index :namespaces, :organization_id, name: INDEX_NAME
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18878,8 +18878,7 @@ CREATE TABLE namespaces (
|
|||
push_rule_id bigint,
|
||||
shared_runners_enabled boolean DEFAULT true NOT NULL,
|
||||
allow_descendants_override_disabled_shared_runners boolean DEFAULT false NOT NULL,
|
||||
traversal_ids integer[] DEFAULT '{}'::integer[] NOT NULL,
|
||||
organization_id bigint DEFAULT 1
|
||||
traversal_ids integer[] DEFAULT '{}'::integer[] NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE namespaces_id_seq
|
||||
|
|
@ -35140,8 +35139,6 @@ CREATE TRIGGER namespaces_loose_fk_trigger AFTER DELETE ON namespaces REFERENCIN
|
|||
|
||||
CREATE TRIGGER nullify_merge_request_metrics_build_data_on_update BEFORE UPDATE ON merge_request_metrics FOR EACH ROW EXECUTE FUNCTION nullify_merge_request_metrics_build_data();
|
||||
|
||||
CREATE TRIGGER organizations_loose_fk_trigger AFTER DELETE ON organizations REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
|
||||
|
||||
CREATE TRIGGER p_ci_builds_loose_fk_trigger AFTER DELETE ON p_ci_builds REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
|
||||
|
||||
CREATE TRIGGER projects_loose_fk_trigger AFTER DELETE ON projects REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
|
||||
|
|
|
|||
|
|
@ -26176,6 +26176,7 @@ State of a Sentry error.
|
|||
| <a id="servicetypeslack_slash_commands_service"></a>`SLACK_SLASH_COMMANDS_SERVICE` | SlackSlashCommandsService type. |
|
||||
| <a id="servicetypesquash_tm_service"></a>`SQUASH_TM_SERVICE` | SquashTmService type. |
|
||||
| <a id="servicetypeteamcity_service"></a>`TEAMCITY_SERVICE` | TeamcityService type. |
|
||||
| <a id="servicetypetelegram_service"></a>`TELEGRAM_SERVICE` | TelegramService type. |
|
||||
| <a id="servicetypeunify_circuit_service"></a>`UNIFY_CIRCUIT_SERVICE` | UnifyCircuitService type. |
|
||||
| <a id="servicetypewebex_teams_service"></a>`WEBEX_TEAMS_SERVICE` | WebexTeamsService type. |
|
||||
| <a id="servicetypeyoutrack_service"></a>`YOUTRACK_SERVICE` | YoutrackService type. |
|
||||
|
|
@ -26359,6 +26360,7 @@ Name of the feature that the callout is for.
|
|||
| <a id="usercalloutfeaturenameenumci_deprecation_warning_for_types_keyword"></a>`CI_DEPRECATION_WARNING_FOR_TYPES_KEYWORD` | Callout feature name for ci_deprecation_warning_for_types_keyword. |
|
||||
| <a id="usercalloutfeaturenameenumcloud_licensing_subscription_activation_banner"></a>`CLOUD_LICENSING_SUBSCRIPTION_ACTIVATION_BANNER` | Callout feature name for cloud_licensing_subscription_activation_banner. |
|
||||
| <a id="usercalloutfeaturenameenumcluster_security_warning"></a>`CLUSTER_SECURITY_WARNING` | Callout feature name for cluster_security_warning. |
|
||||
| <a id="usercalloutfeaturenameenumcode_suggestions_third_party_callout"></a>`CODE_SUGGESTIONS_THIRD_PARTY_CALLOUT` | Callout feature name for code_suggestions_third_party_callout. |
|
||||
| <a id="usercalloutfeaturenameenumcreate_runner_workflow_banner"></a>`CREATE_RUNNER_WORKFLOW_BANNER` | Callout feature name for create_runner_workflow_banner. |
|
||||
| <a id="usercalloutfeaturenameenumeoa_bronze_plan_banner"></a>`EOA_BRONZE_PLAN_BANNER` | Callout feature name for eoa_bronze_plan_banner. |
|
||||
| <a id="usercalloutfeaturenameenumfeature_flags_new_version"></a>`FEATURE_FLAGS_NEW_VERSION` | Callout feature name for feature_flags_new_version. |
|
||||
|
|
|
|||
|
|
@ -412,6 +412,50 @@ Get Datadog integration settings for a project.
|
|||
GET /projects/:id/integrations/datadog
|
||||
```
|
||||
|
||||
## Telegram
|
||||
|
||||
Telegram chat tool.
|
||||
|
||||
### Create/Edit Telegram integration
|
||||
|
||||
Set the Telegram integration for a project.
|
||||
|
||||
```plaintext
|
||||
PUT /projects/:id/integrations/telegram
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `token` | string | true | The Telegram bot token. For example, `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`. |
|
||||
| `room` | string | true | Unique identifier for the target chat or the username of the target channel (in the format `@channelusername`) |
|
||||
| `push_events` | boolean | true | Enable notifications for push events |
|
||||
| `issues_events` | boolean | true | Enable notifications for issue events |
|
||||
| `confidential_issues_events` | boolean | true | Enable notifications for confidential issue events |
|
||||
| `merge_requests_events` | boolean | true | Enable notifications for merge request events |
|
||||
| `tag_push_events` | boolean | true | Enable notifications for tag push events |
|
||||
| `note_events` | boolean | true | Enable notifications for note events |
|
||||
| `confidential_note_events` | boolean | true | Enable notifications for confidential note events |
|
||||
| `pipeline_events` | boolean | true | Enable notifications for pipeline events |
|
||||
| `wiki_page_events` | boolean | true | Enable notifications for wiki page events |
|
||||
|
||||
### Disable Telegram integration
|
||||
|
||||
Disable the Telegram integration for a project. Integration settings are reset.
|
||||
|
||||
```plaintext
|
||||
DELETE /projects/:id/integrations/telegram
|
||||
```
|
||||
|
||||
### Get Telegram integration settings
|
||||
|
||||
Get Telegram integration settings for a project.
|
||||
|
||||
```plaintext
|
||||
GET /projects/:id/integrations/telegram
|
||||
```
|
||||
|
||||
## Unify Circuit
|
||||
|
||||
Unify Circuit RTC and collaboration tool.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ All methods require administrator authorization.
|
|||
|
||||
You can configure the URL endpoint of the system hooks from the GitLab user interface:
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **System Hooks** (`/admin/hooks`).
|
||||
|
||||
Read more about [system hooks](../administration/system_hooks.md).
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ response attributes:
|
|||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/endpoint?parameters"
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
"https://gitlab.example.com/api/v4/endpoint?parameters"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
|
@ -201,9 +202,13 @@ For information about writing attribute descriptions, see the [GraphQL API descr
|
|||
- Wherever needed use this personal access token: `<your_access_token>`.
|
||||
- Always put the request first. `GET` is the default so you don't have to
|
||||
include it.
|
||||
- Use long option names (`--header` instead of `-H`) for legibility. (Tested in
|
||||
[`scripts/lint-doc.sh`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/lint-doc.sh).)
|
||||
- Wrap the URL in double quotes (`"`).
|
||||
- Prefer to use examples using the personal access token and don't pass data of
|
||||
username and password.
|
||||
- For legibility, use the <code>\</code> character and indentation to break long single-line
|
||||
commands apart into multiple lines.
|
||||
|
||||
| Methods | Description |
|
||||
|:------------------------------------------------|:-------------------------------------------------------|
|
||||
|
|
@ -227,7 +232,8 @@ relevant style guide sections on [Fake user information](styleguide/index.md#fak
|
|||
Get the details of a group:
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/gitlab-org"
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
"https://gitlab.example.com/api/v4/groups/gitlab-org"
|
||||
```
|
||||
|
||||
### cURL example with parameters passed in the URL
|
||||
|
|
@ -235,7 +241,8 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
|
|||
Create a new project under the authenticated user's namespace:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects?name=foo"
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
"https://gitlab.example.com/api/v4/projects?name=foo"
|
||||
```
|
||||
|
||||
### Post data using cURL's `--data`
|
||||
|
|
@ -245,7 +252,9 @@ can use cURL's `--data` option. The example below will create a new project
|
|||
`foo` under the authenticated user's namespace.
|
||||
|
||||
```shell
|
||||
curl --data "name=foo" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects"
|
||||
curl --data "name=foo" \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
"https://gitlab.example.com/api/v4/projects"
|
||||
```
|
||||
|
||||
### Post data using JSON content
|
||||
|
|
@ -254,20 +263,23 @@ This example creates a new group. Be aware of the use of single (`'`) and double
|
|||
(`"`) quotes.
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \
|
||||
--data '{"path": "my-group", "name": "My group"}' "https://gitlab.example.com/api/v4/groups"
|
||||
curl --request POST \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data '{"path": "my-group", "name": "My group"}' \
|
||||
"https://gitlab.example.com/api/v4/groups"
|
||||
```
|
||||
|
||||
For readability, you can also set up the `--data` by using the following format:
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
--url "https://gitlab.example.com/api/v4/groups" \
|
||||
--header "content-type: application/json" \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--data '{
|
||||
"path": "my-group",
|
||||
"name": "My group"
|
||||
--url "https://gitlab.example.com/api/v4/groups" \
|
||||
--header "content-type: application/json" \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--data '{
|
||||
"path": "my-group",
|
||||
"name": "My group"
|
||||
}'
|
||||
```
|
||||
|
||||
|
|
@ -277,8 +289,11 @@ Instead of using JSON or URL-encoding data, you can use `multipart/form-data` wh
|
|||
properly handles data encoding:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "title=ssh-key" \
|
||||
--form "key=ssh-rsa AAAAB3NzaC1yc2EA..." "https://gitlab.example.com/api/v4/users/25/keys"
|
||||
curl --request POST \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
--form "title=ssh-key" \
|
||||
--form "key=ssh-rsa AAAAB3NzaC1yc2EA..." \
|
||||
"https://gitlab.example.com/api/v4/users/25/keys"
|
||||
```
|
||||
|
||||
The above example is run by and administrator and will add an SSH public key
|
||||
|
|
@ -292,7 +307,9 @@ contains spaces in its title. Observe how spaces are escaped using the `%20`
|
|||
ASCII code.
|
||||
|
||||
```shell
|
||||
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/42/issues?title=Hello%20GitLab"
|
||||
curl --request POST \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
"https://gitlab.example.com/api/v4/projects/42/issues?title=Hello%20GitLab"
|
||||
```
|
||||
|
||||
Use `%2F` for slashes (`/`).
|
||||
|
|
@ -304,6 +321,9 @@ exclude specific users when requesting a list of users for a project, you would
|
|||
do something like this:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --data "skip_users[]=<user_id>" \
|
||||
--data "skip_users[]=<user_id>" "https://gitlab.example.com/api/v4/projects/<project_id>/users"
|
||||
curl --request PUT \
|
||||
--header "PRIVATE-TOKEN: <your_access_token>"
|
||||
--data "skip_users[]=<user_id>" \
|
||||
--data "skip_users[]=<user_id>" \
|
||||
"https://gitlab.example.com/api/v4/projects/<project_id>/users"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -522,6 +522,11 @@ When using code block style:
|
|||
[list of supported languages and lexers](https://github.com/rouge-ruby/rouge/wiki/List-of-supported-languages-and-lexers)
|
||||
for available syntax highlighters. Use `plaintext` if no better hint is available.
|
||||
|
||||
#### cURL commands in code blocks
|
||||
|
||||
See [cURL commands](../restful_api_styleguide.md#curl-commands) for information
|
||||
about styling cURL commands.
|
||||
|
||||
## Lists
|
||||
|
||||
- Do not use a period if the phrase is not a full sentence.
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ To meet GitLab for Open Source Program requirements, first add an OSI-approved o
|
|||
|
||||
To add a license to a project:
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. On the overview page, select **Add LICENSE**. If the license you want is not available as a license template, manually copy the entire, unaltered [text of your chosen license](https://opensource.org/licenses/) into the `LICENSE` file. GitLab defaults to **All rights reserved** if users do not perform this action.
|
||||
|
||||

|
||||
|
|
@ -45,7 +45,7 @@ Benefits of the GitLab Open Source Program apply to all projects in a GitLab nam
|
|||
|
||||
#### Screenshot 1: License overview
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. On the left sidebar, select your project avatar. If you haven't specified an avatar for your project, the avatar displays as a single letter.
|
||||
1. Take a screenshot of the project overview that clearly displays the license you've chosen for your project.
|
||||
|
||||
|
|
@ -53,8 +53,8 @@ Benefits of the GitLab Open Source Program apply to all projects in a GitLab nam
|
|||
|
||||
#### Screenshot 2: License contents
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Repository** and locate the project's `LICENSE` file.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1.Select **Code > Repository** and locate the project's `LICENSE` file.
|
||||
1. Take a screenshot of the contents of the file. Make sure the screenshot includes the title of the license.
|
||||
|
||||

|
||||
|
|
@ -63,8 +63,8 @@ Benefits of the GitLab Open Source Program apply to all projects in a GitLab nam
|
|||
|
||||
To be eligible for the GitLab Open Source Program, projects must be publicly visible. To check your project's public visibility settings:
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. From the left sidebar, select **Settings > General**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
|
||||
1. Select **Settings > General**.
|
||||
1. Expand **Visibility, project features, permissions**.
|
||||
1. From the **Project visibility** dropdown list, select **Public**.
|
||||
1. Select the **Users can request access** checkbox.
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ Prerequisite:
|
|||
|
||||
To see the status of your GitLab SaaS subscription:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Billing**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Settings > Billing**.
|
||||
|
||||
The following information is displayed:
|
||||
|
||||
|
|
@ -99,8 +99,8 @@ In this case, they would see only the features available to that subscription.
|
|||
|
||||
To view a list of seats being used:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Usage Quotas**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Settings > Usage Quotas**.
|
||||
1. On the **Seats** tab, view usage information.
|
||||
|
||||
The data in seat usage listing, **Seats in use**, and **Seats in subscription** are updated live.
|
||||
|
|
@ -108,8 +108,8 @@ The counts for **Max seats used** and **Seats owed** are updated once per day.
|
|||
|
||||
To view your subscription information and a summary of seat counts:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Billing**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Settings > Billing**.
|
||||
|
||||
The usage statistics are updated once per day, which may cause
|
||||
a difference between the information in the **Usage Quotas** page and the **Billing page**.
|
||||
|
|
@ -136,8 +136,8 @@ For example:
|
|||
|
||||
To export seat usage data as a CSV file:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Billing**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Settings > Billing**.
|
||||
1. Under **Seats currently in use**, select **See usage**.
|
||||
1. Select **Export list**.
|
||||
|
||||
|
|
@ -197,8 +197,8 @@ The following is emailed to you:
|
|||
|
||||
To remove a billable user from your subscription:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Billing**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Settings > Billing**.
|
||||
1. In the **Seats currently in use** section, select **See usage**.
|
||||
1. In the row for the user you want to remove, on the right side, select the ellipsis and **Remove user**.
|
||||
1. Re-type the username and select **Remove user**.
|
||||
|
|
@ -424,8 +424,8 @@ main quota. You can find pricing for additional storage on the
|
|||
|
||||
To purchase additional storage for your group on GitLab SaaS:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Settings > Usage Quotas**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Settings > Usage Quotas**.
|
||||
1. Select **Storage** tab.
|
||||
1. Select **Purchase more storage**.
|
||||
1. Complete the details.
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ Prorated charges are not possible without a quarterly usage report.
|
|||
|
||||
You can view users for your license and determine if you've gone over your subscription.
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left menu, select **Users**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Users**.
|
||||
|
||||
The lists of users are displayed.
|
||||
|
||||
|
|
@ -216,8 +217,9 @@ Example of a license sync request:
|
|||
|
||||
You can manually synchronize your subscription details at any time.
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, select **Subscription**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Subscription**.
|
||||
1. In the **Subscription details** section, select **Sync subscription details**.
|
||||
|
||||
A job is queued. When the job finishes, the subscription details are updated.
|
||||
|
|
@ -226,8 +228,9 @@ A job is queued. When the job finishes, the subscription details are updated.
|
|||
|
||||
If you are an administrator, you can view the status of your subscription:
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, select **Subscription**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Subscription**.
|
||||
|
||||
The **Subscription** page includes the following details:
|
||||
|
||||
|
|
@ -250,8 +253,9 @@ It also displays the following information:
|
|||
|
||||
If you are an administrator, you can export your license usage into a CSV:
|
||||
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, select **Subscription**.
|
||||
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
|
||||
1. Select **Admin Area**.
|
||||
1. Select **Subscription**.
|
||||
1. In the upper-right corner, select **Export license usage file**.
|
||||
|
||||
This file contains the information GitLab uses to manually process quarterly reconciliations or renewals. If your instance is firewalled or an offline environment, you must provide GitLab with this information.
|
||||
|
|
|
|||
|
|
@ -282,8 +282,8 @@ To create group links via filter:
|
|||
|
||||
LDAP user permissions can be manually overridden by an administrator. To override a user's permissions:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Group information > Members**. If LDAP synchronization
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. On the left sidebar, select **Manage > Members**. If LDAP synchronization
|
||||
has granted a user a role with:
|
||||
- More permissions than the parent group membership, that user is displayed as having
|
||||
[direct membership](../project/members/index.md#display-direct-members) of the group.
|
||||
|
|
|
|||
|
|
@ -181,8 +181,8 @@ In lists of group members, entries can display the following badges:
|
|||
|
||||
You can search for members by name, username, or email.
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Above the list of members, in the **Filter members** box, enter search criteria.
|
||||
1. To the right of the **Filter members** box, select the magnifying glass (**{search}**).
|
||||
|
||||
|
|
@ -190,8 +190,8 @@ You can search for members by name, username, or email.
|
|||
|
||||
You can sort members by **Account**, **Access granted**, **Max role**, or **Last sign-in**.
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Above the list of members, in the upper-right corner, from the **Account** list, select
|
||||
the criteria to filter by.
|
||||
1. To switch the sort between ascending and descending, to the right of the **Account** list, select the
|
||||
|
|
@ -205,8 +205,8 @@ Prerequisite:
|
|||
|
||||
- You must have the Owner role.
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Select **Invite members**.
|
||||
1. Fill in the fields.
|
||||
- The role applies to all projects in the group. For more information, see [permissions](../permissions.md).
|
||||
|
|
@ -231,8 +231,8 @@ Prerequisites:
|
|||
|
||||
To remove a member from a group:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Next to the member you want to remove, select **Remove member**.
|
||||
1. Optional. On the **Remove member** confirmation box:
|
||||
- To remove direct user membership from subgroups and projects, select the **Also remove direct user membership from subgroups and projects** checkbox.
|
||||
|
|
|
|||
|
|
@ -41,13 +41,13 @@ You can change the owner of a group. Each group must always have at least one
|
|||
member with the Owner role.
|
||||
|
||||
- As an administrator:
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Give a different member the **Owner** role.
|
||||
1. Refresh the page. You can now remove the **Owner** role from the original owner.
|
||||
- As the current group's owner:
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Give a different member the **Owner** role.
|
||||
1. Have the new owner sign in and remove the **Owner** role from you.
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ To share a given group, for example, `Frontend` with another group, for example,
|
|||
`Engineering`:
|
||||
|
||||
1. Go to the `Frontend` group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Select **Invite a group**.
|
||||
1. In the **Select a group to invite** list, select `Engineering`.
|
||||
1. Select a [role](../permissions.md) as maximum access level.
|
||||
|
|
@ -206,8 +206,8 @@ To disable group mentions:
|
|||
|
||||
You can export a list of members in a group or subgroup as a CSV.
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group or subgroup.
|
||||
1. On the left sidebar, select either **Group information > Members** or **Subgroup information > Members**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group or subgroup.
|
||||
1. On the left sidebar, **Manage > Members**.
|
||||
1. Select **Export as CSV**.
|
||||
1. After the CSV file has been generated, it is emailed as an attachment to the user that requested it.
|
||||
|
||||
|
|
@ -496,8 +496,8 @@ Changes to [group wikis](../project/wiki/group.md) do not appear in group activi
|
|||
|
||||
You can view the most recent actions taken in a group, either in your browser or in an RSS feed:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups > View all groups** and find your group.
|
||||
1. On the left sidebar, select **Group information > Activity**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. On the left sidebar, select **Manage > Activity**.
|
||||
|
||||
To view the activity feed in Atom format, select the
|
||||
**RSS** (**{rss}**) icon.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Prerequisites:
|
|||
To unban a user:
|
||||
|
||||
1. Go to the top-level group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Select the **Banned** tab.
|
||||
1. For the account you want to unban, select **Unban**.
|
||||
|
||||
|
|
@ -43,6 +43,6 @@ Prerequisites:
|
|||
To manually ban a user:
|
||||
|
||||
1. Go to the top-level group.
|
||||
1. On the left sidebar, select **Group information > Members**.
|
||||
1. On the left sidebar, select **Manage > Members**.
|
||||
1. Next to the member you want to ban, select the vertical ellipsis (**{ellipsis_v}**).
|
||||
1. From the dropdown list, select **Ban member**.
|
||||
|
|
|
|||
|
|
@ -160,8 +160,8 @@ Group permissions for a member can be changed only by:
|
|||
|
||||
To see if a member has inherited the permissions from a parent group:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find the group.
|
||||
1. Select **Group information > Members**.
|
||||
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
|
||||
1. Select **Manage > Members**.
|
||||
|
||||
Members list for an example subgroup _Four_:
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ You can configure the following integrations.
|
|||
| [Slack notifications](slack.md) | Send notifications about project events to Slack. | **{dotted-circle}** No |
|
||||
| [Slack slash commands](slack_slash_commands.md) | Enable slash commands in a workspace. | **{dotted-circle}** No |
|
||||
| [Squash TM](squash_tm.md) | Update Squash TM requirements when GitLab issues are modified. | **{check-circle}** Yes |
|
||||
| [Telegram](telegram.md) | Send notifications about project events to Telegram. | **{dotted-circle}** No |
|
||||
| [Unify Circuit](unify_circuit.md) | Send notifications about project events to Unify Circuit. | **{dotted-circle}** No |
|
||||
| [Webex Teams](webex_teams.md) | Receive events notifications. | **{dotted-circle}** No |
|
||||
| [YouTrack](youtrack.md) | Use YouTrack as the issue tracker. | **{dotted-circle}** No |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Import and Integrate
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Telegram **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122879) in GitLab 16.1.
|
||||
|
||||
You can configure GitLab to send notifications to a Telegram chat or channel.
|
||||
To set up the Telegram integration, you must:
|
||||
|
||||
1. [Create a Telegram bot](#create-a-telegram-bot).
|
||||
1. [Configure the Telegram bot](#configure-the-telegram-bot).
|
||||
1. [Set up the Telegram integration in GitLab](#set-up-the-telegram-integration-in-gitlab).
|
||||
|
||||
## Create a Telegram bot
|
||||
|
||||
To create a bot in Telegram:
|
||||
|
||||
1. Start a new chat with `@BotFather`.
|
||||
1. [Create a new bot](https://core.telegram.org/bots/features#creating-a-new-bot) as described in the Telegram documentation.
|
||||
|
||||
When you create a bot, `BotFather` provides you with an API token. Keep this token secure as you need it to authenticate the bot in Telegram.
|
||||
|
||||
## Configure the Telegram bot
|
||||
|
||||
To configure the bot in Telegram:
|
||||
|
||||
1. Add the bot as an administrator to a new or existing channel.
|
||||
1. Assign the bot `Post Messages` rights to receive events.
|
||||
1. Create an identifier for the channel.
|
||||
|
||||
## Set up the Telegram integration in GitLab
|
||||
|
||||
After you invite the bot to a Telegram channel, you can configure GitLab to send notifications:
|
||||
|
||||
1. To enable the integration:
|
||||
- **For your group or project:**
|
||||
1. On the top bar, select **Main menu** and find your group or project.
|
||||
1. on the left sidebar, select **Settings > Integrations**.
|
||||
- **For your instance:**
|
||||
1. On the top bar, select **Main menu > Admin**.
|
||||
1. On the left sidebar, select **Settings > Integrations**.
|
||||
1. Select **Telegram**.
|
||||
1. In **Enable integration**, select the **Active** checkbox.
|
||||
1. In **New token**, [paste the token value from the Telegram bot](#create-a-telegram-bot).
|
||||
1. In the **Trigger** section, select the checkboxes for the GitLab events you want to receive in Telegram.
|
||||
1. In **Channel identifier**, [paste the channel identifier from the Telegram channel](#configure-the-telegram-bot).
|
||||
- To get a private channel ID, use the [`getUpdates`](https://core.telegram.org/bots/api#getupdates) method.
|
||||
1. Optional. Select **Test settings**.
|
||||
1. Select **Save changes**.
|
||||
|
||||
The Telegram channel can now receive all selected GitLab events.
|
||||
|
|
@ -1143,7 +1143,8 @@ Payload example:
|
|||
"key": "NESTOR_PROD_ENVIRONMENT",
|
||||
"value": "us-west-1"
|
||||
}
|
||||
]
|
||||
],
|
||||
"url": "http://example.com/gitlab-org/gitlab-test/-/pipelines/31"
|
||||
},
|
||||
"merge_request": {
|
||||
"id": 1,
|
||||
|
|
|
|||
|
|
@ -183,8 +183,10 @@ Code Suggestions do not prevent you from writing code in your IDE.
|
|||
|
||||
### Internet connectivity
|
||||
|
||||
Code Suggestions only work when you have internet connectivity and can access GitLab.com.
|
||||
Code Suggestions are not available for self-managed customers, nor customers operating within an offline environment.
|
||||
To use Code Suggestions:
|
||||
|
||||
- On GitLab.com, you must have an internet connection and be able to access GitLab.
|
||||
- In GitLab 16.1 and later, on self-managed GitLab, you must have an internet connection. Code Suggestions does not work with offline environments.
|
||||
|
||||
### Model accuracy and quality
|
||||
|
||||
|
|
|
|||
|
|
@ -918,6 +918,21 @@ module API
|
|||
desc: 'The password of the user'
|
||||
}
|
||||
],
|
||||
'telegram' => [
|
||||
{
|
||||
required: true,
|
||||
name: :token,
|
||||
type: String,
|
||||
desc: 'The Telegram chat token. For example, 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11'
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
name: :room,
|
||||
type: String,
|
||||
desc: 'Unique identifier for the target chat or username of the target channel (in the format @channelusername)'
|
||||
},
|
||||
chat_notification_events
|
||||
].flatten,
|
||||
'unify-circuit' => [
|
||||
{
|
||||
required: true,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ module Gitlab
|
|||
finished_at: pipeline.finished_at,
|
||||
duration: pipeline.duration,
|
||||
queued_duration: pipeline.queued_duration,
|
||||
variables: pipeline.variables.map(&:hook_attrs)
|
||||
variables: pipeline.variables.map(&:hook_attrs),
|
||||
url: Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ module Sidebars
|
|||
[
|
||||
:security_dashboard,
|
||||
:vulnerability_report,
|
||||
:dependency_list,
|
||||
:audit_events,
|
||||
:compliance,
|
||||
:scan_policies
|
||||
|
|
|
|||
|
|
@ -9551,6 +9551,9 @@ msgstr ""
|
|||
msgid "Choose file…"
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose protected branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose the top-level group for your repository imports."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -11091,6 +11094,12 @@ msgstr ""
|
|||
msgid "CodeSuggestionsSM|Your personal access token from GitLab.com. See the %{link_start}documentation%{link_end} for information on creating a personal access token."
|
||||
msgstr ""
|
||||
|
||||
msgid "CodeSuggestionsThirdPartyAlert|%{code_suggestions_link_start}Code Suggestions%{link_end} now uses third-party AI services to provide higher quality suggestions. You can %{third_party_link_start}disable third-party services%{link_end} for your group, or disable Code Suggestions entirely in %{profile_settings_link_start}your user profile%{link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "CodeSuggestionsThirdPartyAlert|We use third-party AI services to improve Code Suggestions."
|
||||
msgstr ""
|
||||
|
||||
msgid "CodeSuggestions|%{link_start}What are code suggestions?%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -42007,6 +42016,12 @@ msgstr ""
|
|||
msgid "Select projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select protected branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select protected branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select report"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43739,6 +43754,9 @@ msgstr ""
|
|||
msgid "Specific branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Specific protected branches"
|
||||
msgstr ""
|
||||
|
||||
msgid "Specified URL cannot be used: \"%{reason}\""
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -45147,6 +45165,21 @@ msgstr ""
|
|||
msgid "TeamcityIntegration|Trigger TeamCity CI after every push to the repository, except branch delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "TelegramIntegration|Leave blank to use your current token."
|
||||
msgstr ""
|
||||
|
||||
msgid "TelegramIntegration|New token"
|
||||
msgstr ""
|
||||
|
||||
msgid "TelegramIntegration|Send notifications about project events to Telegram."
|
||||
msgstr ""
|
||||
|
||||
msgid "TelegramIntegration|Send notifications about project events to Telegram. %{docs_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "TelegramIntegration|Unique authentication token."
|
||||
msgstr ""
|
||||
|
||||
msgid "Telephone number"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,12 @@ RUN set -eux; \
|
|||
apt-get autoclean -y
|
||||
|
||||
# Clone GDK and install system dependencies, purge system git
|
||||
ARG GDK_SHA
|
||||
ENV GDK_SHA=${GDK_SHA:-main}
|
||||
RUN set -eux; \
|
||||
git -c advice.detachedHead=false clone --depth 1 --branch ${GDK_BRANCH_OR_TAG:-main} https://gitlab.com/gitlab-org/gitlab-development-kit.git; \
|
||||
git -c advice.detachedHead=false clone --depth 1 https://gitlab.com/gitlab-org/gitlab-development-kit.git; \
|
||||
git -C gitlab-development-kit fetch --depth 1 origin ${GDK_SHA}; \
|
||||
git -C gitlab-development-kit -c advice.detachedHead=false checkout ${GDK_SHA}; \
|
||||
mkdir -p gitlab-development-kit/gitlab && chown -R gdk:gdk gitlab-development-kit; \
|
||||
apt-get update && apt-get install -y --no-install-recommends $(grep -o '^[^#]*' gitlab-development-kit/packages_debian.txt); \
|
||||
apt-get remove -y git git-lfs; \
|
||||
|
|
|
|||
|
|
@ -9,10 +9,22 @@ SHA_TAG="${CI_COMMIT_SHA}"
|
|||
BRANCH_TAG="${CI_COMMIT_REF_SLUG}"
|
||||
BASE_TAG=$([ "${BUILD_GDK_BASE}" == "true" ] && echo "${SHA_TAG}" || echo "master")
|
||||
|
||||
if [[ -z "${GDK_SHA}" ]]; then
|
||||
GDK_SHA=$(git ls-remote https://gitlab.com/gitlab-org/gitlab-development-kit.git main | cut -f 1)
|
||||
fi
|
||||
|
||||
if [[ -n "${CI}" ]]; then
|
||||
OUTPUT_OPTION="--push"
|
||||
else
|
||||
OUTPUT_OPTION="--load"
|
||||
fi
|
||||
|
||||
function build_image() {
|
||||
local image=$1
|
||||
local target=$2
|
||||
|
||||
echoinfo "Using GDK at SHA ${GDK_SHA}"
|
||||
|
||||
docker buildx build \
|
||||
--cache-to="type=inline" \
|
||||
--cache-from="${image}:${BRANCH_TAG}" \
|
||||
|
|
@ -23,7 +35,8 @@ function build_image() {
|
|||
--tag="${image}:${SHA_TAG}" \
|
||||
--tag="${image}:${BRANCH_TAG}" \
|
||||
--build-arg="BASE_TAG=${BASE_TAG}" \
|
||||
--push \
|
||||
--build-arg="GDK_SHA=${GDK_SHA:-main}" \
|
||||
${OUTPUT_OPTION} \
|
||||
.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -319,6 +319,15 @@ FactoryBot.define do
|
|||
token { 'squash_tm_token' }
|
||||
end
|
||||
|
||||
factory :telegram_integration, class: 'Integrations::Telegram' do
|
||||
project
|
||||
type { 'Integrations::Telegram' }
|
||||
active { true }
|
||||
|
||||
token { '123456:ABC-DEF1234' }
|
||||
room { '@channel' }
|
||||
end
|
||||
|
||||
# this is for testing storing values inside properties, which is deprecated and will be removed in
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/29404
|
||||
trait :without_properties_callback do
|
||||
|
|
|
|||
|
|
@ -86,10 +86,11 @@ RSpec.describe 'Profile > Password', feature_category: :user_profile do
|
|||
Rails.application.reload_routes!
|
||||
end
|
||||
|
||||
it 'renders 404' do
|
||||
it 'renders 404', :js do
|
||||
visit edit_profile_password_path
|
||||
|
||||
expect(page).to have_gitlab_http_status(:not_found)
|
||||
expect(page).to have_title('Not Found')
|
||||
expect(page).to have_content('Page Not Found')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe 'User uses inherited settings', :js, feature_category: :integrations do
|
||||
include JiraIntegrationHelpers
|
||||
include ListboxHelpers
|
||||
|
||||
include_context 'project integration activation'
|
||||
|
||||
|
|
@ -24,8 +25,7 @@ RSpec.describe 'User uses inherited settings', :js, feature_category: :integrati
|
|||
expect(page).to have_field('Web URL', with: parent_settings[:url], readonly: true)
|
||||
expect(page).to have_field('New API token or password', with: '', readonly: true)
|
||||
|
||||
click_on 'Use default settings'
|
||||
click_on 'Use custom settings'
|
||||
select_from_listbox('Use custom settings', from: 'Use default settings')
|
||||
|
||||
expect(page).not_to have_button('Use default settings')
|
||||
expect(page).to have_field('Web URL', with: project_settings[:url], readonly: false)
|
||||
|
|
@ -55,8 +55,7 @@ RSpec.describe 'User uses inherited settings', :js, feature_category: :integrati
|
|||
expect(page).to have_field('URL', with: project_settings[:url], readonly: false)
|
||||
expect(page).to have_field('New API token or password', with: '', readonly: false)
|
||||
|
||||
click_on 'Use custom settings'
|
||||
click_on 'Use default settings'
|
||||
select_from_listbox('Use default settings', from: 'Use custom settings')
|
||||
|
||||
expect(page).not_to have_button('Use custom settings')
|
||||
expect(page).to have_field('URL', with: parent_settings[:url], readonly: true)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default function createComponent({
|
|||
Vue.use(Vuex);
|
||||
|
||||
const fakeApollo = createMockApollo([
|
||||
[listQuery, jest.fn().mockResolvedValue(boardListQueryResponse(issuesCount))],
|
||||
[listQuery, jest.fn().mockResolvedValue(boardListQueryResponse({ issuesCount }))],
|
||||
...apolloQueryHandlers,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -964,11 +964,14 @@ export const issueBoardListsQueryResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
export const boardListQueryResponse = (issuesCount = 20) => ({
|
||||
export const boardListQueryResponse = ({
|
||||
listId = 'gid://gitlab/List/5',
|
||||
issuesCount = 20,
|
||||
} = {}) => ({
|
||||
data: {
|
||||
boardList: {
|
||||
__typename: 'BoardList',
|
||||
id: 'gid://gitlab/BoardList/5',
|
||||
id: listId,
|
||||
totalWeight: 5,
|
||||
issuesCount,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1340,8 +1340,8 @@ describe('updateIssueOrder', () => {
|
|||
};
|
||||
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
|
||||
data: {
|
||||
issueMoveList: {
|
||||
issue: rawIssue,
|
||||
issuableMoveList: {
|
||||
issuable: rawIssue,
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
|
|
@ -1355,8 +1355,8 @@ describe('updateIssueOrder', () => {
|
|||
it('should commit MUTATE_ISSUE_SUCCESS mutation when successful', () => {
|
||||
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
|
||||
data: {
|
||||
issueMoveList: {
|
||||
issue: rawIssue,
|
||||
issuableMoveList: {
|
||||
issuable: rawIssue,
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
|
|
@ -1387,8 +1387,8 @@ describe('updateIssueOrder', () => {
|
|||
it('should commit SET_ERROR and dispatch undoMoveIssueCard', () => {
|
||||
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
|
||||
data: {
|
||||
issueMoveList: {
|
||||
issue: {},
|
||||
issuableMoveList: {
|
||||
issuable: {},
|
||||
errors: [{ foo: 'bar' }],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdownItem } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
|
|
@ -21,7 +21,7 @@ describe('External URL Component', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findDropdownItem = () => wrapper.findComponent(GlDropdownItem);
|
||||
const findDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem);
|
||||
|
||||
describe('event hub', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -30,13 +30,13 @@ describe('External URL Component', () => {
|
|||
|
||||
it('should render a dropdown item to delete the environment', () => {
|
||||
expect(findDropdownItem().exists()).toBe(true);
|
||||
expect(wrapper.text()).toEqual('Delete environment');
|
||||
expect(findDropdownItem().attributes('variant')).toBe('danger');
|
||||
expect(findDropdownItem().props('item').text).toBe('Delete environment');
|
||||
expect(findDropdownItem().props('item').extraAttrs.variant).toBe('danger');
|
||||
});
|
||||
|
||||
it('emits requestDeleteEnvironment in the event hub when button is clicked', () => {
|
||||
jest.spyOn(eventHub, '$emit');
|
||||
findDropdownItem().vm.$emit('click');
|
||||
findDropdownItem().vm.$emit('action');
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('requestDeleteEnvironment', resolvedEnvironment);
|
||||
});
|
||||
});
|
||||
|
|
@ -55,13 +55,13 @@ describe('External URL Component', () => {
|
|||
|
||||
it('should render a dropdown item to delete the environment', () => {
|
||||
expect(findDropdownItem().exists()).toBe(true);
|
||||
expect(wrapper.text()).toEqual('Delete environment');
|
||||
expect(findDropdownItem().attributes('variant')).toBe('danger');
|
||||
expect(findDropdownItem().props('item').text).toBe('Delete environment');
|
||||
expect(findDropdownItem().props('item').extraAttrs.variant).toBe('danger');
|
||||
});
|
||||
|
||||
it('emits requestDeleteEnvironment in the event hub when button is clicked', () => {
|
||||
jest.spyOn(mockApollo.defaultClient, 'mutate');
|
||||
findDropdownItem().vm.$emit('click');
|
||||
findDropdownItem().vm.$emit('action');
|
||||
expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({
|
||||
mutation: setEnvironmentToDelete,
|
||||
variables: { environment: resolvedEnvironment },
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdownItem } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import cancelAutoStopMutation from '~/environments/graphql/mutations/cancel_auto_stop.mutation.graphql';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
|
@ -18,6 +18,8 @@ describe('Pin Component', () => {
|
|||
|
||||
const autoStopUrl = '/root/auto-stop-env-test/-/environments/38/cancel_auto_stop';
|
||||
|
||||
const findDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem);
|
||||
|
||||
describe('without graphql', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
|
|
@ -28,14 +30,13 @@ describe('Pin Component', () => {
|
|||
});
|
||||
|
||||
it('should render the component with descriptive text', () => {
|
||||
expect(wrapper.text()).toBe('Prevent auto-stopping');
|
||||
expect(findDropdownItem().props('item').text).toBe('Prevent auto-stopping');
|
||||
});
|
||||
|
||||
it('should emit onPinClick when clicked', () => {
|
||||
const eventHubSpy = jest.spyOn(eventHub, '$emit');
|
||||
const item = wrapper.findComponent(GlDropdownItem);
|
||||
|
||||
item.vm.$emit('click');
|
||||
findDropdownItem().vm.$emit('action');
|
||||
|
||||
expect(eventHubSpy).toHaveBeenCalledWith('cancelAutoStop', autoStopUrl);
|
||||
});
|
||||
|
|
@ -57,14 +58,13 @@ describe('Pin Component', () => {
|
|||
});
|
||||
|
||||
it('should render the component with descriptive text', () => {
|
||||
expect(wrapper.text()).toBe('Prevent auto-stopping');
|
||||
expect(findDropdownItem().props('item').text).toBe('Prevent auto-stopping');
|
||||
});
|
||||
|
||||
it('should emit onPinClick when clicked', () => {
|
||||
jest.spyOn(mockApollo.defaultClient, 'mutate');
|
||||
const item = wrapper.findComponent(GlDropdownItem);
|
||||
|
||||
item.vm.$emit('click');
|
||||
findDropdownItem().vm.$emit('action');
|
||||
|
||||
expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({
|
||||
mutation: cancelAutoStopMutation,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
import { GlDisclosureDropdownItem } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import RollbackComponent from '~/environments/components/environment_rollback.vue';
|
||||
import eventHub from '~/environments/event_hub';
|
||||
|
|
@ -8,10 +8,14 @@ import setEnvironmentToRollback from '~/environments/graphql/mutations/set_envir
|
|||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
||||
describe('Rollback Component', () => {
|
||||
let wrapper;
|
||||
|
||||
const retryUrl = 'https://gitlab.com/retry';
|
||||
|
||||
const findDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem);
|
||||
|
||||
it('Should render Re-deploy label when isLastDeployment is true', () => {
|
||||
const wrapper = shallowMount(RollbackComponent, {
|
||||
wrapper = shallowMount(RollbackComponent, {
|
||||
propsData: {
|
||||
retryUrl,
|
||||
isLastDeployment: true,
|
||||
|
|
@ -19,11 +23,11 @@ describe('Rollback Component', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe('Re-deploy to environment');
|
||||
expect(findDropdownItem().props('item').text).toBe('Re-deploy to environment');
|
||||
});
|
||||
|
||||
it('Should render Rollback label when isLastDeployment is false', () => {
|
||||
const wrapper = shallowMount(RollbackComponent, {
|
||||
wrapper = shallowMount(RollbackComponent, {
|
||||
propsData: {
|
||||
retryUrl,
|
||||
isLastDeployment: false,
|
||||
|
|
@ -31,12 +35,12 @@ describe('Rollback Component', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe('Rollback environment');
|
||||
expect(findDropdownItem().props('item').text).toBe('Rollback environment');
|
||||
});
|
||||
|
||||
it('should emit a "rollback" event on button click', () => {
|
||||
const eventHubSpy = jest.spyOn(eventHub, '$emit');
|
||||
const wrapper = shallowMount(RollbackComponent, {
|
||||
wrapper = shallowMount(RollbackComponent, {
|
||||
propsData: {
|
||||
retryUrl,
|
||||
environment: {
|
||||
|
|
@ -44,9 +48,8 @@ describe('Rollback Component', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
const button = wrapper.findComponent(GlDropdownItem);
|
||||
|
||||
button.vm.$emit('click');
|
||||
findDropdownItem().vm.$emit('action');
|
||||
|
||||
expect(eventHubSpy).toHaveBeenCalledWith('requestRollbackEnvironment', {
|
||||
retryUrl,
|
||||
|
|
@ -63,7 +66,8 @@ describe('Rollback Component', () => {
|
|||
const environment = {
|
||||
name: 'test',
|
||||
};
|
||||
const wrapper = shallowMount(RollbackComponent, {
|
||||
|
||||
wrapper = shallowMount(RollbackComponent, {
|
||||
propsData: {
|
||||
retryUrl,
|
||||
graphql: true,
|
||||
|
|
@ -71,8 +75,8 @@ describe('Rollback Component', () => {
|
|||
},
|
||||
apolloProvider,
|
||||
});
|
||||
const button = wrapper.findComponent(GlDropdownItem);
|
||||
button.vm.$emit('click');
|
||||
|
||||
findDropdownItem().vm.$emit('action');
|
||||
|
||||
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
|
||||
mutation: setEnvironmentToRollback,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ describe('Terminal Component', () => {
|
|||
});
|
||||
|
||||
it('should render a link to open a web terminal with the provided path', () => {
|
||||
const link = wrapper.findByRole('menuitem', { name: __('Terminal') });
|
||||
const link = wrapper.findByRole('link', { name: __('Terminal') });
|
||||
expect(link.attributes('href')).toBe(terminalPath);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
|
|||
it('shows the option to rollback/re-deploy if available', () => {
|
||||
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
|
||||
|
||||
const rollback = wrapper.findByRole('menuitem', {
|
||||
const rollback = wrapper.findByRole('button', {
|
||||
name: s__('Environments|Re-deploy to environment'),
|
||||
});
|
||||
|
||||
|
|
@ -214,7 +214,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
|
|||
apolloProvider: createApolloProvider(),
|
||||
});
|
||||
|
||||
const rollback = wrapper.findByRole('menuitem', {
|
||||
const rollback = wrapper.findByRole('button', {
|
||||
name: s__('Environments|Re-deploy to environment'),
|
||||
});
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
|
|||
});
|
||||
|
||||
it('shows the option to pin the environment if there is an autostop date', () => {
|
||||
const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
|
||||
const pin = wrapper.findByRole('button', { name: __('Prevent auto-stopping') });
|
||||
|
||||
expect(pin.exists()).toBe(true);
|
||||
});
|
||||
|
|
@ -260,7 +260,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
|
|||
it('does not show the option to pin the environment if there is no autostop date', () => {
|
||||
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
|
||||
|
||||
const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
|
||||
const pin = wrapper.findByRole('button', { name: __('Prevent auto-stopping') });
|
||||
|
||||
expect(pin.exists()).toBe(false);
|
||||
});
|
||||
|
|
@ -295,7 +295,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
|
|||
it('does not show the option to pin the environment if there is no autostop date', () => {
|
||||
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
|
||||
|
||||
const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
|
||||
const pin = wrapper.findByRole('button', { name: __('Prevent auto-stopping') });
|
||||
|
||||
expect(pin.exists()).toBe(false);
|
||||
});
|
||||
|
|
@ -319,17 +319,17 @@ describe('~/environments/components/new_environment_item.vue', () => {
|
|||
apolloProvider: createApolloProvider(),
|
||||
});
|
||||
|
||||
const rollback = wrapper.findByRole('menuitem', { name: __('Terminal') });
|
||||
const terminal = wrapper.findByRole('link', { name: __('Terminal') });
|
||||
|
||||
expect(rollback.exists()).toBe(true);
|
||||
expect(terminal.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not show the link to the terminal if not set up', () => {
|
||||
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
|
||||
|
||||
const rollback = wrapper.findByRole('menuitem', { name: __('Terminal') });
|
||||
const terminal = wrapper.findByRole('link', { name: __('Terminal') });
|
||||
|
||||
expect(rollback.exists()).toBe(false);
|
||||
expect(terminal.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -342,21 +342,21 @@ describe('~/environments/components/new_environment_item.vue', () => {
|
|||
apolloProvider: createApolloProvider(),
|
||||
});
|
||||
|
||||
const rollback = wrapper.findByRole('menuitem', {
|
||||
const deleteTrigger = wrapper.findByRole('button', {
|
||||
name: s__('Environments|Delete environment'),
|
||||
});
|
||||
|
||||
expect(rollback.exists()).toBe(true);
|
||||
expect(deleteTrigger.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not show the button to delete the environment if not possible', () => {
|
||||
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
|
||||
|
||||
const rollback = wrapper.findByRole('menuitem', {
|
||||
const deleteTrigger = wrapper.findByRole('button', {
|
||||
name: s__('Environments|Delete environment'),
|
||||
});
|
||||
|
||||
expect(rollback.exists()).toBe(false);
|
||||
expect(deleteTrigger.exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlDropdown, GlLink } from '@gitlab/ui';
|
||||
import { GlCollapsibleListbox, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
|
||||
|
|
@ -27,14 +27,14 @@ describe('OverrideDropdown', () => {
|
|||
};
|
||||
|
||||
const findGlLink = () => wrapper.findComponent(GlLink);
|
||||
const findGlDropdown = () => wrapper.findComponent(GlDropdown);
|
||||
const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
|
||||
describe('template', () => {
|
||||
describe('override prop is true', () => {
|
||||
it('renders GlToggle as disabled', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findGlDropdown().props('text')).toBe('Use custom settings');
|
||||
expect(findGlCollapsibleListbox().props('toggleText')).toBe('Use custom settings');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ describe('OverrideDropdown', () => {
|
|||
it('renders GlToggle as disabled', () => {
|
||||
createComponent({ override: false });
|
||||
|
||||
expect(findGlDropdown().props('text')).toBe('Use default settings');
|
||||
expect(findGlCollapsibleListbox().props('toggleText')).toBe('Use default settings');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
|
|||
expect(attributes[:iid]).to eq(pipeline.iid)
|
||||
expect(attributes[:source]).to eq(pipeline.source)
|
||||
expect(attributes[:status]).to eq(pipeline.status)
|
||||
expect(attributes[:url]).to eq(Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline))
|
||||
expect(attributes[:detailed_status]).to eq('passed')
|
||||
expect(build_data).to be_a(Hash)
|
||||
expect(build_data[:id]).to eq(build.id)
|
||||
|
|
|
|||
|
|
@ -765,6 +765,7 @@ project:
|
|||
- freeze_periods
|
||||
- pumble_integration
|
||||
- webex_teams_integration
|
||||
- telegram_integration
|
||||
- build_report_results
|
||||
- vulnerability_statistic
|
||||
- vulnerability_historical_statistics
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ RSpec.describe Sidebars::Groups::SuperSidebarMenus::SecureMenu, feature_category
|
|||
expect(items.map(&:item_id)).to eq([
|
||||
:security_dashboard,
|
||||
:vulnerability_report,
|
||||
:dependency_list,
|
||||
:audit_events,
|
||||
:compliance,
|
||||
:scan_policies
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Integrations::Telegram, feature_category: :integrations do
|
||||
it_behaves_like "chat integration", "Telegram" do
|
||||
let(:payload) do
|
||||
{
|
||||
text: be_present
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
context 'when integration is active' do
|
||||
before do
|
||||
subject.active = true
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:token) }
|
||||
it { is_expected.to validate_presence_of(:room) }
|
||||
end
|
||||
|
||||
context 'when integration is inactive' do
|
||||
before do
|
||||
subject.active = false
|
||||
end
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:token) }
|
||||
it { is_expected.not_to validate_presence_of(:room) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'before_validation :set_webhook' do
|
||||
context 'when token is not present' do
|
||||
let(:integration) { build(:telegram_integration, token: nil) }
|
||||
|
||||
it 'does not set webhook value' do
|
||||
expect(integration.webhook).to eq(nil)
|
||||
expect(integration).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when token is present' do
|
||||
let(:integration) { create(:telegram_integration) }
|
||||
|
||||
it 'sets webhook value' do
|
||||
expect(integration).to be_valid
|
||||
expect(integration.webhook).to eq("https://api.telegram.org/bot123456:ABC-DEF1234/sendMessage")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2111,15 +2111,48 @@ RSpec.describe Issue, feature_category: :team_planning do
|
|||
end
|
||||
|
||||
describe 'issue_type enum generated methods' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
describe '#<issue_type>?' do
|
||||
let_it_be(:issue) { create(:issue, project: reusable_project) }
|
||||
|
||||
let_it_be(:issue) { create(:issue, project: reusable_project) }
|
||||
where(issue_type: WorkItems::Type.base_types.keys)
|
||||
|
||||
where(issue_type: WorkItems::Type.base_types.keys)
|
||||
with_them do
|
||||
it 'raises an error if called' do
|
||||
expect { issue.public_send("#{issue_type}?".to_sym) }.to raise_error(
|
||||
Issue::ForbiddenColumnUsed,
|
||||
a_string_matching(/`issue\.#{issue_type}\?` uses the `issue_type` column underneath/)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'raises an error if called' do
|
||||
expect { issue.public_send("#{issue_type}?".to_sym) }.to raise_error(Issue::ForbiddenColumnUsed)
|
||||
describe '.<issue_type> scopes' do
|
||||
where(issue_type: WorkItems::Type.base_types.keys)
|
||||
|
||||
with_them do
|
||||
it 'raises an error if called' do
|
||||
expect { Issue.public_send(issue_type.to_sym) }.to raise_error(
|
||||
Issue::ForbiddenColumnUsed,
|
||||
a_string_matching(/`Issue\.#{issue_type}` uses the `issue_type` column underneath/)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when called in a production environment' do
|
||||
before do
|
||||
stub_rails_env('production')
|
||||
end
|
||||
|
||||
it 'returns issues scoped by type instead of raising an error' do
|
||||
issue = create(
|
||||
:issue,
|
||||
issue_type: issue_type,
|
||||
work_item_type: WorkItems::Type.default_by_type(issue_type),
|
||||
project: reusable_project
|
||||
)
|
||||
|
||||
expect(Issue.public_send(issue_type.to_sym)).to contain_exactly(issue)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
let(:repository_storage) { 'default' }
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:organization).class_name('Organizations::Organization') }
|
||||
it { is_expected.to have_many :projects }
|
||||
it { is_expected.to have_many :project_statistics }
|
||||
it { is_expected.to belong_to :parent }
|
||||
|
|
@ -2746,11 +2745,4 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with loose foreign key on organization_id' do
|
||||
it_behaves_like 'cleanup by a loose foreign key' do
|
||||
let!(:parent) { create(:organization) }
|
||||
let!(:model) { create(:namespace, organization: parent) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,11 +6,6 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
|
|||
let_it_be(:organization) { create(:organization) }
|
||||
let_it_be(:default_organization) { create(:organization, :default) }
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to have_many :namespaces }
|
||||
it { is_expected.to have_many :groups }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
subject { create(:organization) }
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
it { is_expected.to have_one(:microsoft_teams_integration) }
|
||||
it { is_expected.to have_one(:mattermost_integration) }
|
||||
it { is_expected.to have_one(:hangouts_chat_integration) }
|
||||
it { is_expected.to have_one(:telegram_integration) }
|
||||
it { is_expected.to have_one(:unify_circuit_integration) }
|
||||
it { is_expected.to have_one(:pumble_integration) }
|
||||
it { is_expected.to have_one(:webex_teams_integration) }
|
||||
|
|
|
|||
Loading…
Reference in New Issue