Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/static-analysis.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/boards/boards_util.js34
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue162
-rw-r--r--app/assets/javascripts/boards/constants.js9
-rw-r--r--app/assets/javascripts/boards/graphql/cache_updates.js118
-rw-r--r--app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql4
-rw-r--r--app/assets/javascripts/boards/stores/actions.js8
-rw-r--r--app/assets/javascripts/environments/components/environment_delete.vue24
-rw-r--r--app/assets/javascripts/environments/components/environment_pin.vue14
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue22
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.vue14
-rw-r--r--app/assets/javascripts/environments/components/new_environment_item.vue18
-rw-r--r--app/assets/javascripts/integrations/edit/components/override_dropdown.vue35
-rw-r--r--app/assets/javascripts/persistent_user_callouts.js1
-rw-r--r--app/models/concerns/issues/forbid_issue_type_column_usage.rb59
-rw-r--r--app/models/integration.rb4
-rw-r--r--app/models/integrations/telegram.rb105
-rw-r--r--app/models/issue.rb23
-rw-r--r--app/models/namespace.rb1
-rw-r--r--app/models/organizations/organization.rb3
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/users/callout.rb3
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml2
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml1
-rw-r--r--app/views/layouts/group.html.haml1
-rw-r--r--app/views/layouts/project.html.haml1
-rw-r--r--config/feature_flags/development/ci_include_components.yml2
-rw-r--r--config/gitlab_loose_foreign_keys.yml4
-rw-r--r--config/metrics/counts_all/20230607170538_projects_telegram_active.yml21
-rw-r--r--config/metrics/counts_all/20230607170951_projects_inheriting_telegram_active.yml21
-rw-r--r--config/metrics/counts_all/20230607171201_instances_telegram_active.yml21
-rw-r--r--config/metrics/counts_all/20230607171312_groups_telegram_active.yml21
-rw-r--r--config/metrics/counts_all/20230607171414_groups_inheriting_telegram_active.yml21
-rw-r--r--db/docs/integrations.yml1
-rw-r--r--db/migrate/20230516044606_add_organization_id_to_namespace.rb6
-rw-r--r--db/migrate/20230516045238_track_organization_record_changes.rb8
-rw-r--r--db/migrate/20230516045442_prepare_index_for_org_id_on_namespaces.rb6
-rw-r--r--db/structure.sql5
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/api/integrations.md44
-rw-r--r--doc/api/system_hooks.md3
-rw-r--r--doc/development/documentation/restful_api_styleguide.md54
-rw-r--r--doc/development/documentation/styleguide/index.md5
-rw-r--r--doc/subscriptions/community_programs.md12
-rw-r--r--doc/subscriptions/gitlab_com/index.md24
-rw-r--r--doc/subscriptions/self_managed/index.md20
-rw-r--r--doc/user/group/access_and_permissions.md4
-rw-r--r--doc/user/group/index.md16
-rw-r--r--doc/user/group/manage.md18
-rw-r--r--doc/user/group/moderate_users.md4
-rw-r--r--doc/user/group/subgroups/index.md4
-rw-r--r--doc/user/project/integrations/index.md1
-rw-r--r--doc/user/project/integrations/telegram.md55
-rw-r--r--doc/user/project/integrations/webhook_events.md3
-rw-r--r--doc/user/project/repository/code_suggestions.md6
-rw-r--r--lib/api/helpers/integrations_helpers.rb15
-rw-r--r--lib/gitlab/data_builder/pipeline.rb3
-rw-r--r--lib/sidebars/groups/super_sidebar_menus/secure_menu.rb1
-rw-r--r--locale/gitlab.pot33
-rw-r--r--qa/gdk/Dockerfile.gdk6
-rwxr-xr-xscripts/build_gdk_image15
-rw-r--r--spec/factories/integrations.rb9
-rw-r--r--spec/features/profiles/password_spec.rb5
-rw-r--r--spec/features/projects/integrations/user_uses_inherited_settings_spec.rb7
-rw-r--r--spec/frontend/boards/board_list_helper.js2
-rw-r--r--spec/frontend/boards/mock_data.js7
-rw-r--r--spec/frontend/boards/stores/actions_spec.js12
-rw-r--r--spec/frontend/environments/environment_delete_spec.js16
-rw-r--r--spec/frontend/environments/environment_pin_spec.js14
-rw-r--r--spec/frontend/environments/environment_rollback_spec.js26
-rw-r--r--spec/frontend/environments/environment_terminal_button_spec.js2
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js26
-rw-r--r--spec/frontend/integrations/edit/components/override_dropdown_spec.js8
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/sidebars/groups/super_sidebar_menus/secure_menu_spec.rb1
-rw-r--r--spec/models/integrations/telegram_spec.rb53
-rw-r--r--spec/models/issue_spec.rb45
-rw-r--r--spec/models/namespace_spec.rb8
-rw-r--r--spec/models/organizations/organization_spec.rb5
-rw-r--r--spec/models/project_spec.rb1
82 files changed, 1122 insertions, 285 deletions
diff --git a/.gitlab/ci/static-analysis.gitlab-ci.yml b/.gitlab/ci/static-analysis.gitlab-ci.yml
index 3d030026e90..b351a63ecf0 100644
--- a/.gitlab/ci/static-analysis.gitlab-ci.yml
+++ b/.gitlab/ci/static-analysis.gitlab-ci.yml
@@ -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:
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
index 93bd97e691b..bf77aa4996c 100644
--- a/app/assets/javascripts/boards/boards_util.js
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -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;
}
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 5f082066ad4..218711346b0 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -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,
+ });
+ }
},
},
};
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index ca188c741a9..d4d1bc7804e 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -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,
},
};
diff --git a/app/assets/javascripts/boards/graphql/cache_updates.js b/app/assets/javascripts/boards/graphql/cache_updates.js
new file mode 100644
index 00000000000..084809e4e60
--- /dev/null
+++ b/app/assets/javascripts/boards/graphql/cache_updates.js
@@ -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,
+ },
+ },
+ }),
+ );
+}
diff --git a/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
index 89670760450..4a46d741a78 100644
--- a/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
@@ -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
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index a144054d680..d96d92948be 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -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);
diff --git a/app/assets/javascripts/environments/components/environment_delete.vue b/app/assets/javascripts/environments/components/environment_delete.vue
index 63169b790c7..6072d923b5f 100644
--- a/app/assets/javascripts/environments/components/environment_delete.vue
+++ b/app/assets/javascripts/environments/components/environment_delete.vue
@@ -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>
diff --git a/app/assets/javascripts/environments/components/environment_pin.vue b/app/assets/javascripts/environments/components/environment_pin.vue
index f5a83b97552..1a63bfa2c1c 100644
--- a/app/assets/javascripts/environments/components/environment_pin.vue
+++ b/app/assets/javascripts/environments/components/environment_pin.vue
@@ -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>
diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue
index f7a853f3128..291d8558a74 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.vue
+++ b/app/assets/javascripts/environments/components/environment_rollback.vue
@@ -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>
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue
index 0df07f0457f..1c4209397b1 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.vue
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue
@@ -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>
diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue
index 72323c0e43e..1f3d429cc3e 100644
--- a/app/assets/javascripts/environments/components/new_environment_item.vue
+++ b/app/assets/javascripts/environments/components/new_environment_item.vue
@@ -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>
diff --git a/app/assets/javascripts/integrations/edit/components/override_dropdown.vue b/app/assets/javascripts/integrations/edit/components/override_dropdown.vue
index 63650400bb7..96ba276033c 100644
--- a/app/assets/javascripts/integrations/edit/components/override_dropdown.vue
+++ b/app/assets/javascripts/integrations/edit/components/override_dropdown.vue
@@ -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>
diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js
index 7cf4906595b..9940575edc3 100644
--- a/app/assets/javascripts/persistent_user_callouts.js
+++ b/app/assets/javascripts/persistent_user_callouts.js
@@ -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 = () => {
diff --git a/app/models/concerns/issues/forbid_issue_type_column_usage.rb b/app/models/concerns/issues/forbid_issue_type_column_usage.rb
new file mode 100644
index 00000000000..46a8a0278d9
--- /dev/null
+++ b/app/models/concerns/issues/forbid_issue_type_column_usage.rb
@@ -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
diff --git a/app/models/integration.rb b/app/models/integration.rb
index fa3a47f8936..bd24f73c882 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -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
diff --git a/app/models/integrations/telegram.rb b/app/models/integrations/telegram.rb
new file mode 100644
index 00000000000..9af12c712c6
--- /dev/null
+++ b/app/models/integrations/telegram.rb
@@ -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
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 044afc1ca64..890af8a27a0 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index fdd88cdc365..7b3bb04da5b 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -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
diff --git a/app/models/organizations/organization.rb b/app/models/organizations/organization.rb
index 79ed4e4cef4..adaa022bef4 100644
--- a/app/models/organizations/organization.rb
+++ b/app/models/organizations/organization.rb
@@ -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 }
diff --git a/app/models/project.rb b/app/models/project.rb
index e5278f7f1ea..7edc5970b5b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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'
diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb
index 41c682649a8..34a116c36be 100644
--- a/app/models/users/callout.rb
+++ b/app/models/users/callout.rb
@@ -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,
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index 983b8160b69..bb398eaf4be 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -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' }
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 0e4c70e2e69..f9b6f462661 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -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' }
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index b528d5685a7..8e52f973e9e 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -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"
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 09eeaa8386d..36e773ea1ae 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -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
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index bb67d8bce14..9a7995090e3 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -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
diff --git a/config/feature_flags/development/ci_include_components.yml b/config/feature_flags/development/ci_include_components.yml
index 262493bc0cc..93637511817 100644
--- a/config/feature_flags/development/ci_include_components.yml
+++ b/config/feature_flags/development/ci_include_components.yml
@@ -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
diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml
index dfc4861d1f7..1f97975a806 100644
--- a/config/gitlab_loose_foreign_keys.yml
+++ b/config/gitlab_loose_foreign_keys.yml
@@ -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
diff --git a/config/metrics/counts_all/20230607170538_projects_telegram_active.yml b/config/metrics/counts_all/20230607170538_projects_telegram_active.yml
new file mode 100644
index 00000000000..0edc5dbde38
--- /dev/null
+++ b/config/metrics/counts_all/20230607170538_projects_telegram_active.yml
@@ -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
diff --git a/config/metrics/counts_all/20230607170951_projects_inheriting_telegram_active.yml b/config/metrics/counts_all/20230607170951_projects_inheriting_telegram_active.yml
new file mode 100644
index 00000000000..d78ddc271a3
--- /dev/null
+++ b/config/metrics/counts_all/20230607170951_projects_inheriting_telegram_active.yml
@@ -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
diff --git a/config/metrics/counts_all/20230607171201_instances_telegram_active.yml b/config/metrics/counts_all/20230607171201_instances_telegram_active.yml
new file mode 100644
index 00000000000..e76b28d7882
--- /dev/null
+++ b/config/metrics/counts_all/20230607171201_instances_telegram_active.yml
@@ -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
diff --git a/config/metrics/counts_all/20230607171312_groups_telegram_active.yml b/config/metrics/counts_all/20230607171312_groups_telegram_active.yml
new file mode 100644
index 00000000000..dbab2be499f
--- /dev/null
+++ b/config/metrics/counts_all/20230607171312_groups_telegram_active.yml
@@ -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
diff --git a/config/metrics/counts_all/20230607171414_groups_inheriting_telegram_active.yml b/config/metrics/counts_all/20230607171414_groups_inheriting_telegram_active.yml
new file mode 100644
index 00000000000..787f88e7cf7
--- /dev/null
+++ b/config/metrics/counts_all/20230607171414_groups_inheriting_telegram_active.yml
@@ -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
diff --git a/db/docs/integrations.yml b/db/docs/integrations.yml
index 889a57a550d..0619a59556f 100644
--- a/db/docs/integrations.yml
+++ b/db/docs/integrations.yml
@@ -50,6 +50,7 @@ classes:
- Integrations::SlackSlashCommands
- Integrations::SquashTm
- Integrations::Teamcity
+- Integrations::Telegram
- Integrations::UnifyCircuit
- Integrations::WebexTeams
- Integrations::Youtrack
diff --git a/db/migrate/20230516044606_add_organization_id_to_namespace.rb b/db/migrate/20230516044606_add_organization_id_to_namespace.rb
index 5569c0edb68..e70ddce197b 100644
--- a/db/migrate/20230516044606_add_organization_id_to_namespace.rb
+++ b/db/migrate/20230516044606_add_organization_id_to_namespace.rb
@@ -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
diff --git a/db/migrate/20230516045238_track_organization_record_changes.rb b/db/migrate/20230516045238_track_organization_record_changes.rb
index 00607421867..90290160d05 100644
--- a/db/migrate/20230516045238_track_organization_record_changes.rb
+++ b/db/migrate/20230516045238_track_organization_record_changes.rb
@@ -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
diff --git a/db/migrate/20230516045442_prepare_index_for_org_id_on_namespaces.rb b/db/migrate/20230516045442_prepare_index_for_org_id_on_namespaces.rb
index 57283050eeb..629dc86caea 100644
--- a/db/migrate/20230516045442_prepare_index_for_org_id_on_namespaces.rb
+++ b/db/migrate/20230516045442_prepare_index_for_org_id_on_namespaces.rb
@@ -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
diff --git a/db/structure.sql b/db/structure.sql
index ae2d55eb4b1..bf437ac26cb 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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();
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index ff5eeafaadf..94767e458dd 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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. |
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index 0d30a2942b1..da19a4de72b 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -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.
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index 720ae457f37..14a078ff68d 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -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).
diff --git a/doc/development/documentation/restful_api_styleguide.md b/doc/development/documentation/restful_api_styleguide.md
index 93afbf7e6dd..4964b66afd1 100644
--- a/doc/development/documentation/restful_api_styleguide.md
+++ b/doc/development/documentation/restful_api_styleguide.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>&#92;</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"
```
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index b64020eb4bd..0c86c1ff055 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -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.
diff --git a/doc/subscriptions/community_programs.md b/doc/subscriptions/community_programs.md
index 18a84ca9826..5e11292a084 100644
--- a/doc/subscriptions/community_programs.md
+++ b/doc/subscriptions/community_programs.md
@@ -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.
![Add license](img/add-license.png)
@@ -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.
![License file](img/license-file.png)
@@ -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.
diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md
index c3593d0b986..28eff9032f0 100644
--- a/doc/subscriptions/gitlab_com/index.md
+++ b/doc/subscriptions/gitlab_com/index.md
@@ -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.
diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md
index abc9760f41d..d6e21c3683b 100644
--- a/doc/subscriptions/self_managed/index.md
+++ b/doc/subscriptions/self_managed/index.md
@@ -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.
diff --git a/doc/user/group/access_and_permissions.md b/doc/user/group/access_and_permissions.md
index a0191ba3369..8aee3c06192 100644
--- a/doc/user/group/access_and_permissions.md
+++ b/doc/user/group/access_and_permissions.md
@@ -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.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index e6d6034e4b2..4119d94597b 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -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.
diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md
index ec419336798..e135964c6e6 100644
--- a/doc/user/group/manage.md
+++ b/doc/user/group/manage.md
@@ -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.
diff --git a/doc/user/group/moderate_users.md b/doc/user/group/moderate_users.md
index 4ec86893524..1dde36c5c70 100644
--- a/doc/user/group/moderate_users.md
+++ b/doc/user/group/moderate_users.md
@@ -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**.
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 4134c541496..12d92196c50 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -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_:
diff --git a/doc/user/project/integrations/index.md b/doc/user/project/integrations/index.md
index 3bdb6ada00e..abc274d814d 100644
--- a/doc/user/project/integrations/index.md
+++ b/doc/user/project/integrations/index.md
@@ -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 |
diff --git a/doc/user/project/integrations/telegram.md b/doc/user/project/integrations/telegram.md
new file mode 100644
index 00000000000..d2e402d0bd0
--- /dev/null
+++ b/doc/user/project/integrations/telegram.md
@@ -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.
diff --git a/doc/user/project/integrations/webhook_events.md b/doc/user/project/integrations/webhook_events.md
index cdbea599a3f..abcbf6009af 100644
--- a/doc/user/project/integrations/webhook_events.md
+++ b/doc/user/project/integrations/webhook_events.md
@@ -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,
diff --git a/doc/user/project/repository/code_suggestions.md b/doc/user/project/repository/code_suggestions.md
index d99191b3b19..8526f032941 100644
--- a/doc/user/project/repository/code_suggestions.md
+++ b/doc/user/project/repository/code_suggestions.md
@@ -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
diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb
index 8e70be68292..8737ba8cfff 100644
--- a/lib/api/helpers/integrations_helpers.rb
+++ b/lib/api/helpers/integrations_helpers.rb
@@ -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,
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index ecb0cc20a64..044d3bb50cd 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -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
diff --git a/lib/sidebars/groups/super_sidebar_menus/secure_menu.rb b/lib/sidebars/groups/super_sidebar_menus/secure_menu.rb
index c79e7e379d0..042e2381744 100644
--- a/lib/sidebars/groups/super_sidebar_menus/secure_menu.rb
+++ b/lib/sidebars/groups/super_sidebar_menus/secure_menu.rb
@@ -19,6 +19,7 @@ module Sidebars
[
:security_dashboard,
:vulnerability_report,
+ :dependency_list,
:audit_events,
:compliance,
:scan_policies
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 710df245300..fd5ba0f523e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/qa/gdk/Dockerfile.gdk b/qa/gdk/Dockerfile.gdk
index 2b5b1137641..cf9cea69056 100644
--- a/qa/gdk/Dockerfile.gdk
+++ b/qa/gdk/Dockerfile.gdk
@@ -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; \
diff --git a/scripts/build_gdk_image b/scripts/build_gdk_image
index 540c60a990e..292d315b5ec 100755
--- a/scripts/build_gdk_image
+++ b/scripts/build_gdk_image
@@ -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} \
.
}
diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb
index 5beb495516f..a927f0fb501 100644
--- a/spec/factories/integrations.rb
+++ b/spec/factories/integrations.rb
@@ -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
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
index c0c573d2f20..05e492e7021 100644
--- a/spec/features/profiles/password_spec.rb
+++ b/spec/features/profiles/password_spec.rb
@@ -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
diff --git a/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb b/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb
index 458f4925051..9d9620c1461 100644
--- a/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb
+++ b/spec/features/projects/integrations/user_uses_inherited_settings_spec.rb
@@ -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)
diff --git a/spec/frontend/boards/board_list_helper.js b/spec/frontend/boards/board_list_helper.js
index 43cf6ead1c1..e3cdec1ab6e 100644
--- a/spec/frontend/boards/board_list_helper.js
+++ b/spec/frontend/boards/board_list_helper.js
@@ -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,
]);
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 68f665e004c..447aacd9cea 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -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,
},
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index b8d3be28ca6..f3800ce8324 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -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' }],
},
},
diff --git a/spec/frontend/environments/environment_delete_spec.js b/spec/frontend/environments/environment_delete_spec.js
index 530f9f55088..ea402f26426 100644
--- a/spec/frontend/environments/environment_delete_spec.js
+++ b/spec/frontend/environments/environment_delete_spec.js
@@ -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 },
diff --git a/spec/frontend/environments/environment_pin_spec.js b/spec/frontend/environments/environment_pin_spec.js
index ee195b41bc8..bf371978d72 100644
--- a/spec/frontend/environments/environment_pin_spec.js
+++ b/spec/frontend/environments/environment_pin_spec.js
@@ -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,
diff --git a/spec/frontend/environments/environment_rollback_spec.js b/spec/frontend/environments/environment_rollback_spec.js
index 5d36209f8a6..653be6c1fde 100644
--- a/spec/frontend/environments/environment_rollback_spec.js
+++ b/spec/frontend/environments/environment_rollback_spec.js
@@ -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,
diff --git a/spec/frontend/environments/environment_terminal_button_spec.js b/spec/frontend/environments/environment_terminal_button_spec.js
index ab9f370595f..0a5ac96d26f 100644
--- a/spec/frontend/environments/environment_terminal_button_spec.js
+++ b/spec/frontend/environments/environment_terminal_button_spec.js
@@ -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);
});
diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js
index 02100046167..eb6990ba8a8 100644
--- a/spec/frontend/environments/new_environment_item_spec.js
+++ b/spec/frontend/environments/new_environment_item_spec.js
@@ -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);
});
});
diff --git a/spec/frontend/integrations/edit/components/override_dropdown_spec.js b/spec/frontend/integrations/edit/components/override_dropdown_spec.js
index 2d1a6b3ace1..a528816971a 100644
--- a/spec/frontend/integrations/edit/components/override_dropdown_spec.js
+++ b/spec/frontend/integrations/edit/components/override_dropdown_spec.js
@@ -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');
});
});
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index eb348f5b497..a379af7483b 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -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)
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 5e4032fbd4b..52de8d39724 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -765,6 +765,7 @@ project:
- freeze_periods
- pumble_integration
- webex_teams_integration
+- telegram_integration
- build_report_results
- vulnerability_statistic
- vulnerability_historical_statistics
diff --git a/spec/lib/sidebars/groups/super_sidebar_menus/secure_menu_spec.rb b/spec/lib/sidebars/groups/super_sidebar_menus/secure_menu_spec.rb
index 9eb81dda462..cdb853876f1 100644
--- a/spec/lib/sidebars/groups/super_sidebar_menus/secure_menu_spec.rb
+++ b/spec/lib/sidebars/groups/super_sidebar_menus/secure_menu_spec.rb
@@ -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
diff --git a/spec/models/integrations/telegram_spec.rb b/spec/models/integrations/telegram_spec.rb
new file mode 100644
index 00000000000..c3a66c84f09
--- /dev/null
+++ b/spec/models/integrations/telegram_spec.rb
@@ -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
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 4bbe0d52486..14d885892ed 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -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)
+
+ 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
- where(issue_type: WorkItems::Type.base_types.keys)
+ 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)
+ 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
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index aea7a3b52c9..e1629dd354e 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -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
diff --git a/spec/models/organizations/organization_spec.rb b/spec/models/organizations/organization_spec.rb
index ec5aea519f5..cc2262634e3 100644
--- a/spec/models/organizations/organization_spec.rb
+++ b/spec/models/organizations/organization_spec.rb
@@ -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) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 51c87217d9d..2164d5fc787 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -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) }