mirror of
https://github.com/mattermost/mattermost.git
synced 2026-08-30 17:06:34 +08:00
[MM-68608] Feature flag Managed Categories (#36345)
* [MM-68608] Feature flag Managed Categories * Update server/channels/api4/channel_test.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Fix i18n --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -112,8 +112,12 @@
|
||||
managed_category_name:
|
||||
type: string
|
||||
description: The name of the managed category to assign this channel to.
|
||||
Requires an Enterprise license and the `EnableManagedChannelCategories`
|
||||
config setting to be enabled.
|
||||
Requires an Enterprise license and the `ManagedChannelCategories` feature flag
|
||||
to be enabled.
|
||||
default_category_name:
|
||||
type: string
|
||||
description: Default sidebar category name for members when joining this channel.
|
||||
Requires `EnableChannelCategorySorting` to be enabled on the server.
|
||||
description: Channel object to be created
|
||||
required: true
|
||||
responses:
|
||||
@@ -683,7 +687,12 @@
|
||||
type: string
|
||||
description: The name of the managed category to assign this channel to.
|
||||
Set to an empty string to clear. Requires an Enterprise license and
|
||||
the `EnableManagedChannelCategories` config setting to be enabled.
|
||||
the `ManagedChannelCategories` feature flag to be enabled.
|
||||
default_category_name:
|
||||
type: string
|
||||
description: Default sidebar category name for members when joining this channel.
|
||||
Set to an empty string to clear. Requires `EnableChannelCategorySorting`
|
||||
to be enabled on the server.
|
||||
description: Channel patch object; include only the fields to update. At least
|
||||
one field must be provided.
|
||||
required: true
|
||||
@@ -1148,8 +1157,8 @@
|
||||
managed category assigned.
|
||||
|
||||
|
||||
Requires an Enterprise license and the `EnableManagedChannelCategories`
|
||||
config setting to be enabled.
|
||||
Requires an Enterprise license and the `ManagedChannelCategories` feature flag
|
||||
to be enabled.
|
||||
|
||||
|
||||
##### Permissions
|
||||
@@ -1175,8 +1184,10 @@
|
||||
description: A map of channel ID to managed category name
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
description: Returned when the `ManagedChannelCategories` feature flag is disabled.
|
||||
"501":
|
||||
description: Returned when the server does not have an Enterprise license.
|
||||
"/api/v4/teams/{team_id}/channels/search":
|
||||
post:
|
||||
tags:
|
||||
|
||||
-631
@@ -1,631 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
async function skipIfNoEnterpriseLicense(adminClient: any) {
|
||||
const license = await adminClient.getClientLicenseOld();
|
||||
test.skip(license.IsLicensed !== 'true', 'Skipping test - server does not have an enterprise license');
|
||||
}
|
||||
|
||||
async function enableManagedCategories(adminClient: any) {
|
||||
await adminClient.patchConfig({
|
||||
TeamSettings: {
|
||||
EnableManagedChannelCategories: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function disableManagedCategories(adminClient: any) {
|
||||
await adminClient.patchConfig({
|
||||
TeamSettings: {
|
||||
EnableManagedChannelCategories: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function createChannelWithManagedCategory(
|
||||
adminClient: any,
|
||||
teamId: string,
|
||||
categoryName: string,
|
||||
channelSuffix: string,
|
||||
) {
|
||||
const channel = await adminClient.createChannel({
|
||||
team_id: teamId,
|
||||
name: `managed-cat-${channelSuffix}-${Date.now()}`,
|
||||
display_name: `Managed ${channelSuffix} ${Date.now()}`,
|
||||
type: 'O',
|
||||
});
|
||||
await adminClient.patchChannel(channel.id, {managed_category_name: categoryName});
|
||||
return channel;
|
||||
}
|
||||
|
||||
test.describe('Managed Channel Categories', () => {
|
||||
/**
|
||||
* @objective Verify that a Channel Admin can assign a managed category to a channel via the channel settings modal,
|
||||
* and the category appears in the sidebar with the channel under it.
|
||||
*/
|
||||
test(
|
||||
'Channel Admin can assign a managed category via channel settings',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup with admin user and enterprise license
|
||||
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
// # Log in and navigate to town-square
|
||||
const {page, channelsPage} = await pw.testBrowser.login(adminUser);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Create a new channel
|
||||
const channelName = `managed-assign-${Date.now()}`;
|
||||
await channelsPage.newChannel(channelName, 'O');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Open channel settings and navigate to info tab
|
||||
const channelSettingsModal = await channelsPage.openChannelSettings();
|
||||
await channelSettingsModal.openInfoTab();
|
||||
|
||||
// * Verify managed category selector is visible
|
||||
const managedSelector = channelSettingsModal.container.locator('.ManagedCategory__control');
|
||||
await expect(managedSelector).toBeVisible();
|
||||
|
||||
// # Click the selector, type a new category name, and select "Create new category"
|
||||
await managedSelector.click();
|
||||
const input = channelSettingsModal.container.getByRole('combobox');
|
||||
await input.fill('Operations');
|
||||
|
||||
const createOption = page.getByRole('option', {name: 'Create new category: Operations'});
|
||||
await expect(createOption).toBeVisible();
|
||||
await createOption.click();
|
||||
|
||||
// # Save and close
|
||||
await channelSettingsModal.save();
|
||||
await pw.wait(pw.duration.two_sec);
|
||||
await channelSettingsModal.close();
|
||||
|
||||
// * Verify the managed category appears in the sidebar with the channel under it
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
await expect(sidebar.getByText('Operations')).toBeVisible();
|
||||
|
||||
const operationsSection = sidebar.locator('.SidebarChannelGroup').filter({hasText: 'Operations'});
|
||||
await expect(operationsSection.locator(`#sidebarItem_${channelName}`)).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that a Channel Admin can remove a managed category from a channel via the channel settings modal,
|
||||
* and the channel returns to the default CHANNELS section.
|
||||
*/
|
||||
test(
|
||||
'Channel Admin can remove a managed category via channel settings',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and create a channel with a managed category
|
||||
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const channel = await createChannelWithManagedCategory(adminClient, team.id, 'Removable', 'remove');
|
||||
await adminClient.addToChannel(adminUser.id, channel.id);
|
||||
|
||||
// # Log in and navigate to the channel
|
||||
const {channelsPage} = await pw.testBrowser.login(adminUser);
|
||||
await channelsPage.goto(team.name, channel.name);
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// * Verify the managed category is visible in the sidebar
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
await expect(sidebar.getByText('Removable')).toBeVisible();
|
||||
|
||||
// # Open channel settings and click the clear button to remove the category
|
||||
const channelSettingsModal = await channelsPage.openChannelSettings();
|
||||
await channelSettingsModal.openInfoTab();
|
||||
|
||||
const clearButton = channelSettingsModal.container.locator('.ManagedCategory__clear-indicator');
|
||||
await expect(clearButton).toBeVisible();
|
||||
await clearButton.click();
|
||||
await pw.wait(pw.duration.half_sec);
|
||||
|
||||
// * Verify the clear button is gone
|
||||
await expect(clearButton).not.toBeVisible();
|
||||
|
||||
// # Save and close
|
||||
await channelSettingsModal.save();
|
||||
await pw.wait(pw.duration.two_sec);
|
||||
await channelSettingsModal.close();
|
||||
|
||||
// * Verify the managed category is removed and the channel is back under CHANNELS
|
||||
await expect(sidebar.getByText('Removable')).not.toBeVisible();
|
||||
|
||||
const channelsSection = sidebar.locator('.SidebarChannelGroup').filter({hasText: 'CHANNELS'});
|
||||
await expect(channelsSection.locator(`#sidebarItem_${channel.name}`)).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that the managed category selector is not visible in channel settings when the feature is disabled.
|
||||
*/
|
||||
test(
|
||||
'managed category selector is not visible when feature is disabled',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and disable managed categories
|
||||
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await disableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
// # Log in and open channel settings
|
||||
const {channelsPage} = await pw.testBrowser.login(adminUser);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
const channelSettingsModal = await channelsPage.openChannelSettings();
|
||||
await channelSettingsModal.openInfoTab();
|
||||
|
||||
// * Verify managed category selector is not visible
|
||||
const managedSelector = channelSettingsModal.container.locator('.ManagedCategory__control');
|
||||
await expect(managedSelector).not.toBeVisible();
|
||||
|
||||
await channelSettingsModal.close();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that a managed category can be assigned to a channel during creation via the new channel modal.
|
||||
*/
|
||||
test('managed category can be assigned when creating a new channel', {tag: '@managed_categories'}, async ({pw}) => {
|
||||
// # Initialize setup and enable managed categories
|
||||
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
// # Log in and open the new channel modal
|
||||
const {page, channelsPage} = await pw.testBrowser.login(adminUser);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
const newChannelModal = await channelsPage.openNewChannelModal();
|
||||
const displayName = `New Managed ${Date.now()}`;
|
||||
await newChannelModal.fillDisplayName(displayName);
|
||||
|
||||
// * Verify managed category selector is visible
|
||||
const managedSelector = newChannelModal.container.locator('.ManagedCategory__control');
|
||||
await expect(managedSelector).toBeVisible();
|
||||
|
||||
// # Select a new managed category and create the channel
|
||||
await managedSelector.click();
|
||||
const input = newChannelModal.container.getByRole('combobox');
|
||||
await input.fill('Flight Ops');
|
||||
|
||||
const createOption = page.getByRole('option', {name: 'Create new category: Flight Ops'});
|
||||
await expect(createOption).toBeVisible();
|
||||
await createOption.click();
|
||||
|
||||
await newChannelModal.create();
|
||||
await channelsPage.toBeVisible();
|
||||
await pw.wait(pw.duration.two_sec);
|
||||
|
||||
// * Verify the managed category appears in the sidebar
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
await expect(sidebar.getByText('Flight Ops')).toBeVisible();
|
||||
});
|
||||
|
||||
/**
|
||||
* @objective Verify that managed categories appear at the top of the sidebar above personal categories like CHANNELS.
|
||||
*/
|
||||
test(
|
||||
'managed categories appear at the top of the sidebar above personal categories',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and create a channel with a managed category
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const channel = await createChannelWithManagedCategory(adminClient, team.id, 'Alpha Priority', 'alpha');
|
||||
await adminClient.addToChannel(user.id, channel.id);
|
||||
|
||||
// # Log in as regular user
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// * Verify the managed category is visible and positioned above CHANNELS
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
const managedCategory = sidebar.getByText('Alpha Priority');
|
||||
await expect(managedCategory).toBeVisible();
|
||||
|
||||
const channelsHeader = sidebar.getByText('CHANNELS', {exact: true});
|
||||
await expect(channelsHeader).toBeVisible();
|
||||
const managedBox = await managedCategory.boundingBox();
|
||||
const channelsBox = await channelsHeader.boundingBox();
|
||||
|
||||
expect(managedBox).toBeTruthy();
|
||||
expect(channelsBox).toBeTruthy();
|
||||
expect(managedBox!.y).toBeLessThan(channelsBox!.y);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that a managed category is only visible to users who are members of at least one channel in it.
|
||||
*/
|
||||
test(
|
||||
'managed category is only visible when user is a member of a channel in it',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and create a channel with a managed category (without adding the user)
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
await createChannelWithManagedCategory(adminClient, team.id, 'Secret Ops', 'secret');
|
||||
|
||||
// # Log in as regular user who is not a member of the channel
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// * Verify the managed category is not visible
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
await expect(sidebar.getByText('Secret Ops')).not.toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that channels within a managed category are sorted alphabetically by display name.
|
||||
*/
|
||||
test('managed categories sort channels alphabetically', {tag: '@managed_categories'}, async ({pw}) => {
|
||||
// # Initialize setup and create two channels with the same managed category
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const suffix = Date.now();
|
||||
const channelB = await adminClient.createChannel({
|
||||
team_id: team.id,
|
||||
name: `bravo-${suffix}`,
|
||||
display_name: `Bravo Channel`,
|
||||
type: 'O',
|
||||
});
|
||||
const channelA = await adminClient.createChannel({
|
||||
team_id: team.id,
|
||||
name: `alpha-${suffix}`,
|
||||
display_name: `Alpha Channel`,
|
||||
type: 'O',
|
||||
});
|
||||
|
||||
// # Assign both to the same managed category and add user
|
||||
await adminClient.patchChannel(channelB.id, {managed_category_name: 'Sorted Category'});
|
||||
await adminClient.patchChannel(channelA.id, {managed_category_name: 'Sorted Category'});
|
||||
|
||||
await adminClient.addToChannel(user.id, channelA.id);
|
||||
await adminClient.addToChannel(user.id, channelB.id);
|
||||
|
||||
// # Log in as regular user
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// * Verify both channels are visible and Alpha appears before Bravo
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
await expect(sidebar.getByText('Sorted Category')).toBeVisible();
|
||||
|
||||
const alphaItem = sidebar.locator(`#sidebarItem_${channelA.name}`);
|
||||
const bravoItem = sidebar.locator(`#sidebarItem_${channelB.name}`);
|
||||
|
||||
await expect(alphaItem).toBeVisible();
|
||||
await expect(bravoItem).toBeVisible();
|
||||
|
||||
const alphaBox = await alphaItem.boundingBox();
|
||||
const bravoBox = await bravoItem.boundingBox();
|
||||
|
||||
expect(alphaBox).toBeTruthy();
|
||||
expect(bravoBox).toBeTruthy();
|
||||
expect(alphaBox!.y).toBeLessThan(bravoBox!.y);
|
||||
});
|
||||
|
||||
/**
|
||||
* @objective Verify that the favorite button is disabled for channels in managed categories.
|
||||
*/
|
||||
test('channels in managed categories cannot be favorited', {tag: '@managed_categories'}, async ({pw}) => {
|
||||
// # Initialize setup and create a channel with a managed category
|
||||
const {adminUser, adminClient, team} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const channel = await createChannelWithManagedCategory(adminClient, team.id, 'No Favorites', 'nofav');
|
||||
await adminClient.addToChannel(adminUser.id, channel.id);
|
||||
|
||||
// # Log in and navigate to the managed channel
|
||||
const {channelsPage} = await pw.testBrowser.login(adminUser);
|
||||
await channelsPage.goto(team.name, channel.name);
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// * Verify the favorite button is visible but disabled
|
||||
const favoriteButton = channelsPage.page.locator('#toggleFavorite');
|
||||
await expect(favoriteButton).toBeVisible();
|
||||
await expect(favoriteButton).toBeDisabled();
|
||||
});
|
||||
|
||||
/**
|
||||
* @objective Verify that managed category headers do not show a context menu on right-click.
|
||||
*/
|
||||
test('managed categories do not show a context menu', {tag: '@managed_categories'}, async ({pw}) => {
|
||||
// # Initialize setup and create a channel with a managed category
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const channel = await createChannelWithManagedCategory(adminClient, team.id, 'No Menu', 'nomenu');
|
||||
await adminClient.addToChannel(user.id, channel.id);
|
||||
|
||||
// # Log in as regular user
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Right-click on the managed category header
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
const categoryHeader = sidebar.getByText('No Menu');
|
||||
await expect(categoryHeader).toBeVisible();
|
||||
|
||||
await categoryHeader.click({button: 'right'});
|
||||
await pw.wait(pw.duration.one_sec);
|
||||
|
||||
// * Verify no context menu appears
|
||||
const categoryMenu = channelsPage.page.locator('.SidebarCategoryMenu');
|
||||
await expect(categoryMenu).not.toBeVisible();
|
||||
});
|
||||
|
||||
/**
|
||||
* @objective Verify that the Favorite menu item is disabled in the channel options menu for channels in managed categories.
|
||||
*/
|
||||
test(
|
||||
'channel context menu shows favorite as disabled in managed category',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and create a channel with a managed category
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const channel = await createChannelWithManagedCategory(adminClient, team.id, 'Context Menu', 'ctx');
|
||||
await adminClient.addToChannel(user.id, channel.id);
|
||||
|
||||
// # Log in as regular user
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Open the channel options menu via the three-dot button
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
const channelItem = sidebar.locator(`#sidebarItem_${channel.name}`);
|
||||
await expect(channelItem).toBeVisible();
|
||||
|
||||
await channelItem.hover();
|
||||
const menuButton = channelItem.getByRole('button', {name: /Channel options/});
|
||||
await menuButton.click();
|
||||
|
||||
// * Verify the Favorite menu item is visible but disabled
|
||||
const favoriteMenuItem = channelsPage.page.getByRole('menuitem', {name: /Favorite/i});
|
||||
await expect(favoriteMenuItem).toBeVisible();
|
||||
|
||||
const isDisabled = await favoriteMenuItem.evaluate((el) => {
|
||||
return el.classList.contains('Mui-disabled') || el.getAttribute('aria-disabled') === 'true';
|
||||
});
|
||||
expect(isDisabled).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that the Move To menu item is disabled for non-admin users on channels in managed categories.
|
||||
*/
|
||||
test(
|
||||
'Move To is disabled for non-admin users on channels in managed categories',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and create a channel with a managed category
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const channel = await createChannelWithManagedCategory(adminClient, team.id, 'No Move', 'nomove');
|
||||
await adminClient.addToChannel(user.id, channel.id);
|
||||
|
||||
// # Log in as regular user
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// # Open the channel options menu via the three-dot button
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
const channelItem = sidebar.locator(`#sidebarItem_${channel.name}`);
|
||||
await expect(channelItem).toBeVisible();
|
||||
|
||||
await channelItem.hover();
|
||||
const menuButton = channelItem.getByRole('button', {name: /Channel options/});
|
||||
await menuButton.click();
|
||||
|
||||
// * Verify the Move To menu item is visible but disabled
|
||||
const moveToMenuItem = channelsPage.page.getByRole('menuitem', {name: /Move to/i});
|
||||
await expect(moveToMenuItem).toBeVisible();
|
||||
|
||||
const isDisabled = await moveToMenuItem.evaluate((el) => {
|
||||
return el.classList.contains('Mui-disabled') || el.getAttribute('aria-disabled') === 'true';
|
||||
});
|
||||
expect(isDisabled).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that assigning the same managed category name to multiple channels groups them under a single
|
||||
* category header in the sidebar.
|
||||
*/
|
||||
test(
|
||||
'assigning the same category name to multiple channels groups them together',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and create two channels with the same managed category
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const suffix = Date.now();
|
||||
const channel1 = await createChannelWithManagedCategory(
|
||||
adminClient,
|
||||
team.id,
|
||||
'Shared Category',
|
||||
`shared1-${suffix}`,
|
||||
);
|
||||
const channel2 = await createChannelWithManagedCategory(
|
||||
adminClient,
|
||||
team.id,
|
||||
'Shared Category',
|
||||
`shared2-${suffix}`,
|
||||
);
|
||||
|
||||
// # Add the user to both channels
|
||||
await adminClient.addToChannel(user.id, channel1.id);
|
||||
await adminClient.addToChannel(user.id, channel2.id);
|
||||
|
||||
// # Log in as regular user
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// * Verify only one category header exists and both channels are under it
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
const categories = sidebar.getByText('Shared Category');
|
||||
await expect(categories).toHaveCount(1);
|
||||
|
||||
await expect(sidebar.locator(`#sidebarItem_${channel1.name}`)).toBeVisible();
|
||||
await expect(sidebar.locator(`#sidebarItem_${channel2.name}`)).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that when an admin assigns a managed category to a channel, the category appears in the
|
||||
* sidebar of other users in real-time via websocket.
|
||||
*/
|
||||
test(
|
||||
'managed category appears in real-time when admin assigns a channel to it',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and create a channel without a managed category
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const channel = await adminClient.createChannel({
|
||||
team_id: team.id,
|
||||
name: `realtime-${Date.now()}`,
|
||||
display_name: `Realtime Channel ${Date.now()}`,
|
||||
type: 'O',
|
||||
});
|
||||
await adminClient.addToChannel(user.id, channel.id);
|
||||
|
||||
// # Log in as regular user
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// * Verify the managed category does not exist yet
|
||||
const sidebar = channelsPage.sidebarLeft.container;
|
||||
await expect(sidebar.getByText('Realtime Ops')).not.toBeVisible();
|
||||
|
||||
// # Admin assigns a managed category to the channel via API
|
||||
await adminClient.patchChannel(channel.id, {managed_category_name: 'Realtime Ops'});
|
||||
|
||||
// * Verify the managed category appears in real-time
|
||||
await pw.waitUntil(
|
||||
async () => {
|
||||
return await sidebar
|
||||
.getByText('Realtime Ops')
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
},
|
||||
{timeout: 10000},
|
||||
);
|
||||
|
||||
await expect(sidebar.getByText('Realtime Ops')).toBeVisible();
|
||||
await expect(sidebar.locator(`#sidebarItem_${channel.name}`)).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that the Enable Managed Channel Categories setting is available in the System Console
|
||||
* under Site Configuration > Users and Teams.
|
||||
*/
|
||||
test(
|
||||
'Enable Managed Channel Categories setting is available in System Console',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup
|
||||
const {adminUser, adminClient} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
|
||||
// # Log in and navigate to the System Console
|
||||
const {systemConsolePage} = await pw.testBrowser.login(adminUser);
|
||||
await systemConsolePage.goto();
|
||||
await systemConsolePage.toBeVisible();
|
||||
|
||||
// # Navigate to Users and Teams
|
||||
await systemConsolePage.sidebar.siteConfiguration.usersAndTeams.click();
|
||||
await systemConsolePage.usersAndTeams.toBeVisible();
|
||||
|
||||
// * Verify the setting is visible
|
||||
const setting = systemConsolePage.usersAndTeams.container.getByTestId(
|
||||
'TeamSettings.EnableManagedChannelCategoriestrue',
|
||||
);
|
||||
await expect(setting).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that a non-channel-admin user sees the managed category selector as disabled in channel settings.
|
||||
*/
|
||||
test(
|
||||
'non-channel-admin sees the managed category selector as disabled',
|
||||
{tag: '@managed_categories'},
|
||||
async ({pw}) => {
|
||||
// # Initialize setup and create a channel with a managed category
|
||||
const {adminUser, adminClient, team, user} = await pw.initSetup({withDefaultProfileImage: false});
|
||||
await skipIfNoEnterpriseLicense(adminClient);
|
||||
await enableManagedCategories(adminClient);
|
||||
await adminClient.addToTeam(team.id, adminUser.id);
|
||||
|
||||
const channel = await createChannelWithManagedCategory(adminClient, team.id, 'Locked', 'locked');
|
||||
await adminClient.addToChannel(user.id, channel.id);
|
||||
|
||||
// # Log in as regular user and open channel settings
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, channel.name);
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
const channelSettingsModal = await channelsPage.openChannelSettings();
|
||||
await channelSettingsModal.openInfoTab();
|
||||
|
||||
// * Verify the managed category selector is disabled
|
||||
const disabledControl = channelSettingsModal.container.locator('.ManagedCategory__control--is-disabled');
|
||||
await expect(disabledControl).toBeVisible();
|
||||
|
||||
await channelSettingsModal.close();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -36,7 +36,9 @@ func (api *API) InitChannel() {
|
||||
api.BaseRoutes.ChannelsForTeam.Handle("/search", api.APISessionRequiredDisableWhenBusy(searchChannelsForTeam)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.ChannelsForTeam.Handle("/autocomplete", api.APISessionRequired(autocompleteChannelsForTeam)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ChannelsForTeam.Handle("/search_autocomplete", api.APISessionRequired(autocompleteChannelsForTeamForSearch)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ChannelsForTeam.Handle("/managed_categories", api.APISessionRequired(getManagedCategories)).Methods(http.MethodGet)
|
||||
if api.srv.Config().FeatureFlags.ManagedChannelCategories {
|
||||
api.BaseRoutes.ChannelsForTeam.Handle("/managed_categories", api.APISessionRequired(getManagedCategories)).Methods(http.MethodGet)
|
||||
}
|
||||
api.BaseRoutes.User.Handle("/teams/{team_id:[A-Za-z0-9]+}/channels", api.APISessionRequired(getChannelsForTeamForUser)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.User.Handle("/channels", api.APISessionRequired(getChannelsForUser)).Methods(http.MethodGet)
|
||||
|
||||
@@ -430,7 +432,7 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if updatingManagedCategory {
|
||||
if model.MinimumEnterpriseLicense(c.App.Channels().License()) && *c.App.Config().TeamSettings.EnableManagedChannelCategories {
|
||||
if model.MinimumEnterpriseLicense(c.App.Channels().License()) && c.App.Config().FeatureFlags.ManagedChannelCategories {
|
||||
if ok, _ := c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), c.Params.ChannelId, model.PermissionManageChannelRoles); !ok {
|
||||
c.Err = model.NewAppError("patchChannel", "api.channel.patch_update_channel.cannot_update_managed_category.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
@@ -454,7 +456,7 @@ func patchChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if updatingManagedCategory {
|
||||
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) || !*c.App.Config().TeamSettings.EnableManagedChannelCategories {
|
||||
if !model.MinimumEnterpriseLicense(c.App.Channels().License()) || !c.App.Config().FeatureFlags.ManagedChannelCategories {
|
||||
c.Logger.Info("Managed category update ignored: feature not available")
|
||||
} else {
|
||||
name := *patch.ManagedCategoryName
|
||||
@@ -2934,11 +2936,6 @@ func getManagedCategories(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if !*c.App.Config().TeamSettings.EnableManagedChannelCategories {
|
||||
c.Err = model.NewAppError("Api4.getManagedCategories", "api.managed_category.feature_not_available.app_error", nil, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
mappings, appErr := c.App.GetVisibleManagedCategoryMappings(c.AppContext, teamID)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
|
||||
@@ -335,7 +335,13 @@ func TestCreateChannel(t *testing.T) {
|
||||
|
||||
func TestCreateChannelManagedCategory(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
th := SetupConfig(t, func(cfg *model.Config) {
|
||||
cfg.FeatureFlags.ManagedChannelCategories = true
|
||||
}).InitBasic(t)
|
||||
th.ConfigStore.SetReadOnlyFF(false)
|
||||
t.Cleanup(func() {
|
||||
th.ConfigStore.SetReadOnlyFF(true)
|
||||
})
|
||||
client := th.Client
|
||||
team := th.BasicTeam
|
||||
|
||||
@@ -358,7 +364,7 @@ func TestCreateChannelManagedCategory(t *testing.T) {
|
||||
|
||||
t.Run("should ignore managed category when feature is disabled", func(t *testing.T) {
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = false })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.ManagedChannelCategories = false })
|
||||
defer func() {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
@@ -379,7 +385,7 @@ func TestCreateChannelManagedCategory(t *testing.T) {
|
||||
|
||||
t.Run("should set managed category when feature is enabled with license", func(t *testing.T) {
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.ManagedChannelCategories = true })
|
||||
defer func() {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
@@ -7463,7 +7469,9 @@ func TestSetChannelMembers(t *testing.T) {
|
||||
|
||||
func TestGetManagedCategories(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
th := SetupConfig(t, func(cfg *model.Config) {
|
||||
cfg.FeatureFlags.ManagedChannelCategories = true
|
||||
}).InitBasic(t)
|
||||
client := th.Client
|
||||
|
||||
t.Run("should return 501 without enterprise license", func(t *testing.T) {
|
||||
@@ -7472,26 +7480,12 @@ func TestGetManagedCategories(t *testing.T) {
|
||||
require.Equal(t, http.StatusNotImplemented, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("should return 403 when feature is disabled", func(t *testing.T) {
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
||||
defer func() {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = false })
|
||||
|
||||
resp, err := client.DoAPIGet(context.Background(), fmt.Sprintf("/teams/%s/channels/managed_categories", th.BasicTeam.Id), "")
|
||||
require.Error(t, err)
|
||||
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("should return empty map when no managed categories exist", func(t *testing.T) {
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
||||
defer func() {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = true })
|
||||
|
||||
resp, err := client.DoAPIGet(context.Background(), fmt.Sprintf("/teams/%s/channels/managed_categories", th.BasicTeam.Id), "")
|
||||
require.NoError(t, err)
|
||||
@@ -7508,7 +7502,6 @@ func TestGetManagedCategories(t *testing.T) {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = true })
|
||||
|
||||
appErr := th.App.SetChannelManagedCategory(th.Context, th.BasicChannel.Id, "Operations")
|
||||
require.Nil(t, appErr)
|
||||
@@ -7526,17 +7519,42 @@ func TestGetManagedCategories(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetManagedCategoriesFeatureFlagDisabled(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := SetupConfig(t, func(cfg *model.Config) {
|
||||
cfg.FeatureFlags.ManagedChannelCategories = false
|
||||
}).InitBasic(t)
|
||||
|
||||
t.Run("route is not registered when feature flag is off at startup", func(t *testing.T) {
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
||||
defer func() {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
}()
|
||||
|
||||
resp, err := th.Client.DoAPIGet(context.Background(), fmt.Sprintf("/teams/%s/channels/managed_categories", th.BasicTeam.Id), "")
|
||||
require.Error(t, err)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchChannelManagedCategory(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
th := SetupConfig(t, func(cfg *model.Config) {
|
||||
cfg.FeatureFlags.ManagedChannelCategories = true
|
||||
}).InitBasic(t)
|
||||
th.ConfigStore.SetReadOnlyFF(false)
|
||||
t.Cleanup(func() {
|
||||
th.ConfigStore.SetReadOnlyFF(true)
|
||||
})
|
||||
client := th.Client
|
||||
|
||||
enableManagedCategories := func() {
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.ManagedChannelCategories = true })
|
||||
}
|
||||
disableManagedCategories := func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = false })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.ManagedChannelCategories = false })
|
||||
}
|
||||
removeLicense := func() {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
|
||||
@@ -17,9 +17,10 @@ import (
|
||||
const maxPropertyValuePatchItems = 50
|
||||
|
||||
func (api *API) InitProperties() {
|
||||
api.BaseRoutes.PropertyFields.Handle("", api.APISessionRequired(getPropertyFields)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.PropertyValues.Handle("", api.APISessionRequired(getPropertyValues)).Methods(http.MethodGet)
|
||||
if api.srv.Config().FeatureFlags.IntegratedBoards {
|
||||
if api.srv.Config().FeatureFlags.IntegratedBoards || api.srv.Config().FeatureFlags.ManagedChannelCategories {
|
||||
api.BaseRoutes.PropertyFields.Handle("", api.APISessionRequired(getPropertyFields)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.PropertyValues.Handle("", api.APISessionRequired(getPropertyValues)).Methods(http.MethodGet)
|
||||
|
||||
api.BaseRoutes.PropertyFields.Handle("", api.APISessionRequired(createPropertyField)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.PropertyField.Handle("", api.APISessionRequired(patchPropertyField)).Methods(http.MethodPatch)
|
||||
api.BaseRoutes.PropertyField.Handle("", api.APISessionRequired(deletePropertyField)).Methods(http.MethodDelete)
|
||||
|
||||
@@ -304,7 +304,7 @@ func (a *App) CreateChannel(rctx request.CTX, channel *model.Channel, addMember
|
||||
}
|
||||
|
||||
if channel.ManagedCategoryName != "" {
|
||||
if !model.MinimumEnterpriseLicense(a.Channels().License()) || !model.SafeDereference(a.Config().TeamSettings.EnableManagedChannelCategories) {
|
||||
if !model.MinimumEnterpriseLicense(a.Channels().License()) || !a.Config().FeatureFlags.ManagedChannelCategories {
|
||||
rctx.Logger().Warn("Managed category update ignored: feature not available")
|
||||
sc.ManagedCategoryName = ""
|
||||
} else {
|
||||
|
||||
@@ -3604,6 +3604,10 @@ func TestPluginAPICreateChannelManagedCategory(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
|
||||
th := Setup(t).InitBasic(t)
|
||||
th.ConfigStore.SetReadOnlyFF(false)
|
||||
t.Cleanup(func() {
|
||||
th.ConfigStore.SetReadOnlyFF(true)
|
||||
})
|
||||
api := th.SetupPluginAPI()
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
||||
@@ -3611,8 +3615,8 @@ func TestPluginAPICreateChannelManagedCategory(t *testing.T) {
|
||||
appErr := th.App.Srv().RemoveLicense()
|
||||
require.Nil(t, appErr)
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = true })
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableManagedChannelCategories = false })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.ManagedChannelCategories = true })
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.ManagedChannelCategories = false })
|
||||
|
||||
categoryName := "Operations"
|
||||
channel := &model.Channel{
|
||||
|
||||
@@ -242,7 +242,6 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li
|
||||
props["MobilePreventScreenCapture"] = strconv.FormatBool(*c.NativeAppSettings.MobilePreventScreenCapture)
|
||||
props["MobileJailbreakProtection"] = strconv.FormatBool(*c.NativeAppSettings.MobileJailbreakProtection)
|
||||
props["ExperimentalEnableWatermark"] = strconv.FormatBool(*c.ExperimentalSettings.EnableWatermark)
|
||||
props["EnableManagedChannelCategories"] = strconv.FormatBool(*c.TeamSettings.EnableManagedChannelCategories)
|
||||
}
|
||||
|
||||
if model.MinimumEnterpriseAdvancedLicense(license) {
|
||||
|
||||
@@ -2576,10 +2576,6 @@
|
||||
"id": "api.license_error",
|
||||
"translation": "api endpoint requires a license"
|
||||
},
|
||||
{
|
||||
"id": "api.managed_category.feature_not_available.app_error",
|
||||
"translation": "Managed channel categories are not available."
|
||||
},
|
||||
{
|
||||
"id": "api.marshal_error",
|
||||
"translation": "Failed to marshal."
|
||||
|
||||
@@ -2424,7 +2424,6 @@ type TeamSettings struct {
|
||||
// In seconds.
|
||||
UserStatusAwayTimeout *int64 `access:"experimental_features"`
|
||||
MaxChannelsPerTeam *int64 `access:"site_users_and_teams"`
|
||||
EnableManagedChannelCategories *bool `access:"site_users_and_teams"`
|
||||
MaxNotificationsPerChannel *int64 `access:"environment_push_notification_server"`
|
||||
EnableConfirmNotificationsToChannel *bool `access:"site_notifications"`
|
||||
TeammateNameDisplay *string `access:"site_users_and_teams"`
|
||||
@@ -2497,10 +2496,6 @@ func (s *TeamSettings) SetDefaults() {
|
||||
s.MaxChannelsPerTeam = NewPointer(int64(2000))
|
||||
}
|
||||
|
||||
if s.EnableManagedChannelCategories == nil {
|
||||
s.EnableManagedChannelCategories = NewPointer(false)
|
||||
}
|
||||
|
||||
if s.MaxNotificationsPerChannel == nil {
|
||||
s.MaxNotificationsPerChannel = NewPointer(int64(1000))
|
||||
}
|
||||
|
||||
@@ -104,6 +104,9 @@ type FeatureFlags struct {
|
||||
|
||||
// Enable LIKE-based CJK (Chinese, Japanese, Korean) search for PostgreSQL
|
||||
CJKSearch bool
|
||||
|
||||
// ManagedChannelCategories enables server-side managed sidebar category enforcement (Enterprise).
|
||||
ManagedChannelCategories bool
|
||||
}
|
||||
|
||||
func (f *FeatureFlags) SetDefaults() {
|
||||
@@ -152,6 +155,8 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.IntegratedBoards = false
|
||||
|
||||
f.CJKSearch = false
|
||||
|
||||
f.ManagedChannelCategories = false
|
||||
}
|
||||
|
||||
// ToMap returns the feature flags as a map[string]string
|
||||
|
||||
@@ -2700,14 +2700,6 @@ const AdminDefinition: AdminDefinitionType = {
|
||||
placeholder: defineMessage({id: 'admin.team.maxChannelsExample', defaultMessage: 'E.g.: "100"'}),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.USERS_AND_TEAMS)),
|
||||
},
|
||||
{
|
||||
type: 'bool',
|
||||
key: 'TeamSettings.EnableManagedChannelCategories',
|
||||
label: defineMessage({id: 'admin.team.managedChannelCategoriesTitle', defaultMessage: 'Managed channel categories:'}),
|
||||
help_text: defineMessage({id: 'admin.team.managedChannelCategoriesDescription', defaultMessage: 'Enables teams to have fixed sidebar categories to organize channels for all members of the team. Channel admins can create categories and organize their channels.'}),
|
||||
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.USERS_AND_TEAMS)),
|
||||
isHidden: it.not(it.minLicenseTier(LicenseSkus.Enterprise)),
|
||||
},
|
||||
{
|
||||
type: 'bool',
|
||||
key: 'TeamSettings.EnableJoinLeaveMessageByDefault',
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import ManagedCategorySelector from './managed_category_selector';
|
||||
const baseState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {EnableManagedChannelCategories: 'true'},
|
||||
config: {FeatureFlagManagedChannelCategories: 'true'},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'team1',
|
||||
|
||||
@@ -3309,8 +3309,6 @@
|
||||
"admin.team.invalidateEmailInvitesTitle": "Invalidate pending email invites",
|
||||
"admin.team.lastActiveTimeDescription": "When enabled, last active time allows users to see when someone was last online.",
|
||||
"admin.team.lastActiveTimeTitle": "Enable last active time: ",
|
||||
"admin.team.managedChannelCategoriesDescription": "Enables teams to have fixed sidebar categories to organize channels for all members of the team. Channel admins can create categories and organize their channels.",
|
||||
"admin.team.managedChannelCategoriesTitle": "Managed channel categories:",
|
||||
"admin.team.maxChannelsDescription": "Maximum total number of channels per team, including both active and archived channels.",
|
||||
"admin.team.maxChannelsExample": "E.g.: \"100\"",
|
||||
"admin.team.maxChannelsTitle": "Max Channels Per Team:",
|
||||
|
||||
+7
-7
@@ -1453,7 +1453,7 @@ describe('isChannelInManagedCategory', () => {
|
||||
const state = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {EnableManagedChannelCategories: 'true'},
|
||||
config: {FeatureFlagManagedChannelCategories: 'true'},
|
||||
},
|
||||
channels: {
|
||||
channels: {
|
||||
@@ -1491,7 +1491,7 @@ describe('getChannelManagedCategoryName', () => {
|
||||
const state = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {EnableManagedChannelCategories: 'true'},
|
||||
config: {FeatureFlagManagedChannelCategories: 'true'},
|
||||
},
|
||||
channels: {
|
||||
channels: {
|
||||
@@ -1527,7 +1527,7 @@ describe('makeGetManagedCategoriesForTeam', () => {
|
||||
const state = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {EnableManagedChannelCategories: 'true'},
|
||||
config: {FeatureFlagManagedChannelCategories: 'true'},
|
||||
},
|
||||
channelCategories: {
|
||||
managedCategoryMappings: {},
|
||||
@@ -1547,7 +1547,7 @@ describe('makeGetManagedCategoriesForTeam', () => {
|
||||
const state = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {EnableManagedChannelCategories: 'true'},
|
||||
config: {FeatureFlagManagedChannelCategories: 'true'},
|
||||
},
|
||||
channelCategories: {
|
||||
managedCategoryMappings: {
|
||||
@@ -1610,7 +1610,7 @@ describe('makeGetCategoriesForTeam (merged)', () => {
|
||||
const state = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {EnableManagedChannelCategories: 'true'},
|
||||
config: {FeatureFlagManagedChannelCategories: 'true'},
|
||||
},
|
||||
channelCategories: {
|
||||
byId: {favorites1: nonManagedCategory1, channels1: nonManagedCategory2},
|
||||
@@ -1633,7 +1633,7 @@ describe('makeGetCategoriesForTeam (merged)', () => {
|
||||
const state = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {EnableManagedChannelCategories: 'true'},
|
||||
config: {FeatureFlagManagedChannelCategories: 'true'},
|
||||
},
|
||||
channelCategories: {
|
||||
byId: {favorites1: nonManagedCategory1, channels1: nonManagedCategory2},
|
||||
@@ -1672,7 +1672,7 @@ describe('makeGetCategoriesForTeam (merged)', () => {
|
||||
const state = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {EnableManagedChannelCategories: 'true'},
|
||||
config: {FeatureFlagManagedChannelCategories: 'true'},
|
||||
},
|
||||
channelCategories: {
|
||||
byId: {favorites1: nonManagedCategory1, channels1: nonManagedCategory2},
|
||||
|
||||
+1
-1
@@ -475,7 +475,7 @@ function isUnreadChannel(
|
||||
}
|
||||
|
||||
export function areManagedCategoriesEnabled(state: GlobalState): boolean {
|
||||
return getConfig(state).EnableManagedChannelCategories === 'true';
|
||||
return getConfig(state).FeatureFlagManagedChannelCategories === 'true';
|
||||
}
|
||||
|
||||
export function getManagedCategoryMappings(state: GlobalState, teamId: string): Record<string, string> | undefined {
|
||||
|
||||
@@ -64,7 +64,6 @@ export type ClientConfig = {
|
||||
EnableExperimentalLocales: string;
|
||||
EnableUserStatuses: string;
|
||||
EnableLastActiveTime: string;
|
||||
EnableManagedChannelCategories: string;
|
||||
EnableTimedDND: string;
|
||||
EnableCrossTeamSearch: 'true' | 'false';
|
||||
EnableCustomTermsOfService: string;
|
||||
@@ -133,6 +132,7 @@ export type ClientConfig = {
|
||||
FeatureFlagWebSocketEventScope: string;
|
||||
FeatureFlagInteractiveDialogAppsForm: string;
|
||||
FeatureFlagContentFlagging: string;
|
||||
FeatureFlagManagedChannelCategories: string;
|
||||
|
||||
ForgotPasswordLink: string;
|
||||
GiphySdkKey: string;
|
||||
|
||||
Reference in New Issue
Block a user