Merge branch 'master' into boards-playbooks-i18n-sync

Этот коммит содержится в:
Caleb Roseland
2023-04-07 09:31:29 -05:00
родитель 4a5e5b7b96 9bffaeae42
Коммит 8f9b22b004
452 изменённых файлов: 4480 добавлений и 4111 удалений

12
.github/PULL_REQUEST_TEMPLATE.md поставляемый
Просмотреть файл

@@ -19,6 +19,18 @@ If this pull request addresses a Help Wanted ticket, please link the relevant Gi
Otherwise, link the JIRA ticket. Otherwise, link the JIRA ticket.
--> -->
#### Screenshots
<!--
If the PR includes UI changes, include screenshots/GIFs.
For an easier comparison of UI changes a table (template below) can be used.
| before | after |
|----|----|
| <insert before screenshot here> | <insert after screenshot here> |
-->
#### Release Note #### Release Note
<!-- <!--
Add a release note for each of the following conditions: Add a release note for each of the following conditions:

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('admin console', () => { describe('admin console', {testIsolation: true}, () => {
let testUser; let testUser;
let testTeam; let testTeam;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('api > runs', () => { describe('api > runs', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -11,7 +11,7 @@
import {onlyOn} from '@cypress/skip-test'; import {onlyOn} from '@cypress/skip-test';
describe('channels > App Bar', () => { describe('channels > App Bar', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > broadcast', () => { describe('channels > broadcast', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testAdmin; let testAdmin;

Просмотреть файл

@@ -11,7 +11,7 @@
import {onlyOn} from '@cypress/skip-test'; import {onlyOn} from '@cypress/skip-test';
describe('channels > channel header', () => { describe('channels > channel header', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -10,7 +10,7 @@
// Group: @playbooks // Group: @playbooks
import * as TIMEOUTS from '../../../fixtures/timeouts'; import * as TIMEOUTS from '../../../fixtures/timeouts';
describe('channels > general actions', () => { describe('channels > general actions', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testSysadmin; let testSysadmin;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > actions', () => { describe('channels > actions', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testSysadmin; let testSysadmin;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > post type components', () => { describe('channels > post type components', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testChannel; let testChannel;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > retrospective', () => { describe('runs > retrospective', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybookWithMetrics; let testPlaybookWithMetrics;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > rhs > header', () => { describe('channels > rhs > header', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -11,7 +11,7 @@
import {HALF_SEC, ONE_SEC} from '../../../../fixtures/timeouts'; import {HALF_SEC, ONE_SEC} from '../../../../fixtures/timeouts';
describe('channels > rhs > checklist', () => { describe('channels > rhs > checklist', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;
@@ -164,7 +164,7 @@ describe('channels > rhs > checklist', () => {
}); });
// * Verify the expected error message. // * Verify the expected error message.
cy.verifyEphemeralMessage('Failed to execute slash command /invalid'); cy.verifyEphemeralMessage('Failed to find slash command /invalid');
}); });
it('successfully runs a valid slash command', () => { it('successfully runs a valid slash command', () => {

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > rhs > header', () => { describe('channels > rhs > header', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > rhs > home', () => { describe('channels > rhs > home', {testIsolation: true}, () => {
let testSysadmin; let testSysadmin;
let testTeam; let testTeam;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > rhs > runlist', () => { describe('channels > rhs > runlist', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels rhs > start a run', () => { describe('channels rhs > start a run', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testChannel; let testChannel;

Просмотреть файл

@@ -13,7 +13,7 @@
import * as TIMEOUTS from '../../../../fixtures/timeouts'; import * as TIMEOUTS from '../../../../fixtures/timeouts';
describe('channels > rhs > status update', () => { describe('channels > rhs > status update', {testIsolation: true}, () => {
const defaultReminderMessage = '# Default reminder message'; const defaultReminderMessage = '# Default reminder message';
let testTeam; let testTeam;
let testChannel; let testChannel;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > rhs > template', () => { describe('channels > rhs > template', {testIsolation: true}, () => {
let team1; let team1;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > rhs > title', () => { describe('channels > rhs > title', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -11,7 +11,7 @@
import * as TIMEOUTS from '../../../fixtures/timeouts'; import * as TIMEOUTS from '../../../fixtures/timeouts';
describe('channels > rhs', () => { describe('channels > rhs', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > run dialog', () => { describe('channels > run dialog', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > run', () => { describe('channels > run', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPrivateChannel; let testPrivateChannel;

Просмотреть файл

@@ -11,7 +11,7 @@
import {switchToChannel} from '../../../channels/mark_as_unread/helpers'; import {switchToChannel} from '../../../channels/mark_as_unread/helpers';
describe('channels > slash command > owner', () => { describe('channels > slash command > owner', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testUser2; let testUser2;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > slash command > info', () => { describe('channels > slash command > info', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testUser2; let testUser2;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > slash command > owner', () => { describe('channels > slash command > owner', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testUser2; let testUser2;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > slash command > test', () => { describe('channels > slash command > test', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testUser2; let testUser2;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('channels > slash command > todo', () => { describe('channels > slash command > todo', {testIsolation: true}, () => {
let team1; let team1;
let team2; let team2;
let testUser; let testUser;

Просмотреть файл

@@ -11,7 +11,7 @@
import * as TIMEOUTS from '../../../fixtures/timeouts'; import * as TIMEOUTS from '../../../fixtures/timeouts';
describe('channels > update request post', () => { describe('channels > update request post', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testParticipant; let testParticipant;
let testChannelMemberOnly; let testChannelMemberOnly;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('digest messages', () => { describe('digest messages', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -14,7 +14,7 @@
import {HALF_SEC} from '../../fixtures/timeouts'; import {HALF_SEC} from '../../fixtures/timeouts';
import {stubClipboard} from '../../utils'; import {stubClipboard} from '../../utils';
describe('lhs', () => { describe('lhs', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPublicPlaybook; let testPublicPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('navigation', () => { describe('navigation', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('playbooks > edit', () => { describe('playbooks > edit', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testUser2; let testUser2;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('playbooks > creation button', () => { describe('playbooks > creation button', {testIsolation: true}, () => {
let testSysadmin; let testSysadmin;
let testTeam; let testTeam;
let testUser; let testUser;

Просмотреть файл

@@ -11,7 +11,7 @@
// //
import * as TIMEOUTS from '../../../../fixtures/timeouts'; import * as TIMEOUTS from '../../../../fixtures/timeouts';
describe('playbooks > edit > task actions', () => { describe('playbooks > edit > task actions', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testUser2; let testUser2;

Просмотреть файл

@@ -11,7 +11,7 @@
/* eslint-disable no-only-tests/no-only-tests */ /* eslint-disable no-only-tests/no-only-tests */
describe('playbooks > edit_metrics', () => { describe('playbooks > edit_metrics', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;

Просмотреть файл

@@ -13,7 +13,7 @@
import * as TIMEOUTS from '../../../fixtures/timeouts'; import * as TIMEOUTS from '../../../fixtures/timeouts';
describe('playbooks > edit', () => { describe('playbooks > edit', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testSysadmin; let testSysadmin;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('playbooks > feedback', () => { describe('playbooks > feedback', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('playbooks > list', () => { describe('playbooks > list', {testIsolation: true}, () => {
const playbookTitle = 'The Playbook Name'; const playbookTitle = 'The Playbook Name';
let testTeam; let testTeam;
let testUser; let testUser;

Просмотреть файл

@@ -11,7 +11,7 @@
import {stubClipboard} from '../../../utils'; import {stubClipboard} from '../../../utils';
describe('playbooks > overview', () => { describe('playbooks > overview', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testOtherTeam; let testOtherTeam;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('playbooks > list pagination', () => { describe('playbooks > list pagination', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
const ExtraPlaybooks = 20; const ExtraPlaybooks = 20;

Просмотреть файл

@@ -11,7 +11,7 @@
const RUN_NAME_MAX_LENGTH = 64; const RUN_NAME_MAX_LENGTH = 64;
describe('playbooks > start a run', () => { describe('playbooks > start a run', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('playbooks > edit status update', () => { describe('playbooks > edit status update', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPlaybook; let testPlaybook;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > list', () => { describe('runs > list', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testAnotherUser; let testAnotherUser;

Просмотреть файл

@@ -13,7 +13,7 @@
import {getRandomId} from '../../../utils'; import {getRandomId} from '../../../utils';
describe('runs > permissions', () => { describe('runs > permissions', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testOtherTeam; let testOtherTeam;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > run details page', () => { describe('runs > run details page', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testPublicPlaybook; let testPublicPlaybook;

Просмотреть файл

@@ -12,7 +12,7 @@
// Note that this test checks the basic behavior in Run details page as participant / viewer // Note that this test checks the basic behavior in Run details page as participant / viewer
// It relies on the Channel RHS Checklist test to cover the full behavior of the checklists // It relies on the Channel RHS Checklist test to cover the full behavior of the checklists
describe('runs > run details page > checklist', () => { describe('runs > run details page > checklist', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > run details page > finish', () => { describe('runs > run details page > finish', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -13,7 +13,7 @@
import {stubClipboard} from '../../../utils'; import {stubClipboard} from '../../../utils';
describe('runs > run details page > header', () => { describe('runs > run details page > header', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > run details page > restart run', () => { describe('runs > run details page > restart run', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -62,7 +62,7 @@ const verifyMetricInput = (index, title, target, description, placeholder) => {
const getRetro = () => cy.findByTestId('run-retrospective-section'); const getRetro = () => cy.findByTestId('run-retrospective-section');
describe('runs > run details page', () => { describe('runs > run details page', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -11,7 +11,7 @@
/* eslint-disable no-only-tests/no-only-tests */ /* eslint-disable no-only-tests/no-only-tests */
describe('runs > run details page > status update', () => { describe('runs > run details page > status update', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > run details page > summary', () => { describe('runs > run details page > summary', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testRun; let testRun;

Просмотреть файл

@@ -11,7 +11,7 @@
import * as TIMEOUTS from '../../../fixtures/timeouts'; import * as TIMEOUTS from '../../../fixtures/timeouts';
describe('runs > task actions', () => { describe('runs > task actions', {testIsolation: true}, () => {
let testPlaybook; let testPlaybook;
let testTeam; let testTeam;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > run details page > rhs > participants', () => { describe('runs > run details page > rhs > participants', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testUser2; let testUser2;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > run details page > run info', () => { describe('runs > run details page > run info', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > run details page > RHS', () => { describe('runs > run details page > RHS', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('runs > run details page > status update', () => { describe('runs > run details page > status update', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testViewerUser; let testViewerUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('Task Inbox >', () => { describe('Task Inbox >', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;

Просмотреть файл

@@ -9,7 +9,7 @@
// Stage: @prod // Stage: @prod
// Group: @playbooks // Group: @playbooks
describe('playbook tour points', () => { describe('playbook tour points', {testIsolation: true}, () => {
let testTeam; let testTeam;
let testUser; let testUser;
let testSysadmin; let testSysadmin;

Просмотреть файл

@@ -176,7 +176,7 @@ Cypress.Commands.add('updateStatus', (message, reminderQuery) => {
if (reminderQuery) { if (reminderQuery) {
cy.get('#reminder_timer_datetime').within(() => { cy.get('#reminder_timer_datetime').within(() => {
cy.get('input').type(reminderQuery, {delay: TIMEOUTS.TWO_HUNDRED_MILLIS, force: true}).type('{enter}', {force: true}); cy.get('input').type(reminderQuery, {delay: TIMEOUTS.TWO_HUNDRED_MILLIS, force: true}).wait(TIMEOUTS.ONE_SEC).type('{enter}');
}); });
} }

Просмотреть файл

@@ -77,10 +77,13 @@ func TestSetConfiguration(t *testing.T) {
}) })
t.Run("test enable shared boards", func(t *testing.T) { t.Run("test enable shared boards", func(t *testing.T) {
baseProductSettings := &model.ProductSettings{
EnablePublicSharedBoards: &falseRef,
}
mmConfig := baseConfig mmConfig := baseConfig
mmConfig.PluginSettings.Plugins = make(map[string]map[string]interface{}) mmConfig.ProductSettings = *baseProductSettings
mmConfig.PluginSettings.Plugins[server.PluginName] = make(map[string]interface{}) mmConfig.ProductSettings.EnablePublicSharedBoards = &boolTrue
mmConfig.PluginSettings.Plugins[server.PluginName][server.SharedBoardsName] = true
config := server.CreateBoardsConfig(*mmConfig, "", "") config := server.CreateBoardsConfig(*mmConfig, "", "")
assert.Equal(t, true, config.EnablePublicSharedBoards) assert.Equal(t, true, config.EnablePublicSharedBoards)
}) })

Просмотреть файл

@@ -59,8 +59,8 @@ func CreateBoardsConfig(mmconfig mm_model.Config, baseURL string, serverID strin
} }
enablePublicSharedBoards := false enablePublicSharedBoards := false
if mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true { if mmconfig.ProductSettings.EnablePublicSharedBoards != nil {
enablePublicSharedBoards = true enablePublicSharedBoards = *mmconfig.ProductSettings.EnablePublicSharedBoards
} }
enableBoardsDeletion := false enableBoardsDeletion := false

Просмотреть файл

@@ -1,3 +1,3 @@
save-exact=true save-exact=true
legacy-peer-deps=true legacy-peer-deps=true
global-style=true global-style=true

Просмотреть файл

@@ -102,12 +102,11 @@
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@types/react-transition-group": "4.4.4", "@types/react-transition-group": "4.4.4",
"@types/redux-mock-store": "1.0.3", "@types/redux-mock-store": "1.0.3",
"@typescript-eslint/eslint-plugin": "5.16.0", "@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.16.0", "@typescript-eslint/parser": "5.57.1",
"babel-eslint": "10.1.0", "babel-eslint": "10.1.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "6.7.1", "css-loader": "6.7.1",
"eslint": "^8.11.0",
"eslint-import-resolver-webpack": "0.13.2", "eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-babel": "^5.3.1", "eslint-plugin-babel": "^5.3.1",
"eslint-plugin-formatjs": "4.9.0", "eslint-plugin-formatjs": "4.9.0",

Просмотреть файл

@@ -873,7 +873,7 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
> >
<div <div
class="octo-board-header-cell KanbanColumnHeader" class="octo-board-header-cell KanbanColumnHeader"
draggable="true" draggable="false"
style="opacity: 1;" style="opacity: 1;"
> >
<span <span
@@ -901,7 +901,7 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
</div> </div>
<div <div
class="octo-board-header-cell KanbanColumnHeader" class="octo-board-header-cell KanbanColumnHeader"
draggable="true" draggable="false"
style="opacity: 1;" style="opacity: 1;"
> >
<span <span
@@ -1927,7 +1927,7 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
> >
<div <div
class="octo-board-header-cell KanbanColumnHeader" class="octo-board-header-cell KanbanColumnHeader"
draggable="true" draggable="false"
style="opacity: 1;" style="opacity: 1;"
> >
<span <span
@@ -1955,7 +1955,7 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
</div> </div>
<div <div
class="octo-board-header-cell KanbanColumnHeader" class="octo-board-header-cell KanbanColumnHeader"
draggable="true" draggable="false"
style="opacity: 1;" style="opacity: 1;"
> >
<span <span

Просмотреть файл

@@ -29,7 +29,14 @@ jest.mock('react-router-dom', () => {
return { return {
...originalModule, ...originalModule,
useRouteMatch: jest.fn(() => { useRouteMatch: jest.fn(() => {
return {url: '/board/view'} return {
params: {
teamId: 'team1',
boardId: 'boardId1',
viewId: 'viewId1',
cardId: 'cardId1',
},
}
}), }),
} }
}) })

Просмотреть файл

@@ -469,7 +469,7 @@ exports[`src/components/kanban/kanbanColumnHeader should match snapshot readonly
<div> <div>
<div <div
class="octo-board-header-cell KanbanColumnHeader" class="octo-board-header-cell KanbanColumnHeader"
draggable="true" draggable="false"
style="opacity: 1;" style="opacity: 1;"
> >
<span <span

Просмотреть файл

@@ -52,8 +52,12 @@ const defaultProperty: IPropertyTemplate = {
export default function KanbanColumnHeader(props: Props): JSX.Element { export default function KanbanColumnHeader(props: Props): JSX.Element {
const intl = useIntl() const intl = useIntl()
const {board, activeView, group, groupByProperty} = props const {board, activeView, group, groupByProperty} = props
let readonly = props.readonly
if(!readonly){
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
}
const [groupTitle, setGroupTitle] = useState(group.option.value) const [groupTitle, setGroupTitle] = useState(group.option.value)
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
const canEditOption = groupByProperty?.type !== 'person' && group.option.id const canEditOption = groupByProperty?.type !== 'person' && group.option.id
const headerRef = useRef<HTMLDivElement>(null) const headerRef = useRef<HTMLDivElement>(null)
@@ -79,7 +83,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
setGroupTitle(group.option.value) setGroupTitle(group.option.value)
}, [group.option.value]) }, [group.option.value])
if (canEditBoardProperties) { if (!readonly) {
drop(drag(headerRef)) drop(drag(headerRef))
} }
@@ -97,7 +101,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
ref={headerRef} ref={headerRef}
style={{opacity: isDragging ? 0.5 : 1}} style={{opacity: isDragging ? 0.5 : 1}}
className={className} className={className}
draggable={!props.readonly && canEditBoardProperties} draggable={!readonly}
> >
{!group.option.id && {!group.option.id &&
<Label <Label
@@ -133,7 +137,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
onCancel={() => { onCancel={() => {
setGroupTitle(group.option.value) setGroupTitle(group.option.value)
}} }}
readonly={props.readonly || !canEditBoardProperties} readonly={readonly}
spellCheck={true} spellCheck={true}
/> />
</Label>} </Label>}
@@ -145,7 +149,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
onMenuClose={props.onCalculationMenuClose} onMenuClose={props.onCalculationMenuClose}
onMenuOpen={props.onCalculationMenuOpen} onMenuOpen={props.onCalculationMenuOpen}
cardProperties={board.cardProperties} cardProperties={board.cardProperties}
readonly={props.readonly || !canEditBoardProperties} readonly={readonly}
onChange={(data: {calculation: string, propertyId: string}) => { onChange={(data: {calculation: string, propertyId: string}) => {
if (data.calculation === calculationValue && data.propertyId === calculationProperty.id) { if (data.calculation === calculationValue && data.propertyId === calculationProperty.id) {
return return

Просмотреть файл

@@ -983,11 +983,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Regene
> >
<a <a
class="shareUrl" class="shareUrl"
href="http://localhost:8065/team/team-id/shared/1/1?r=anotherToken" href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=anotherToken"
rel="noreferrer" rel="noreferrer"
target="_blank" target="_blank"
> >
http://localhost:8065/team/team-id/shared/1/1?r=anotherToken http://localhost:8065/boards/public/team/team-id/shared/1/1?r=anotherToken
</a> </a>
<div <div
class="octo-tooltip tooltip-top" class="octo-tooltip tooltip-top"
@@ -3188,11 +3188,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoard, and click switc
> >
<a <a
class="shareUrl" class="shareUrl"
href="http://localhost:8065/team/team-id/shared/1/1?r=oneToken" href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=oneToken"
rel="noreferrer" rel="noreferrer"
target="_blank" target="_blank"
> >
http://localhost:8065/team/team-id/shared/1/1?r=oneToken http://localhost:8065/boards/public/team/team-id/shared/1/1?r=oneToken
</a> </a>
<div <div
class="octo-tooltip tooltip-top" class="octo-tooltip tooltip-top"
@@ -3461,11 +3461,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoardComponent and cli
> >
<a <a
class="shareUrl" class="shareUrl"
href="http://localhost:8065/team/team-id/shared/1/1?r=aToken" href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=aToken"
rel="noreferrer" rel="noreferrer"
target="_blank" target="_blank"
> >
http://localhost:8065/team/team-id/shared/1/1?r=aToken http://localhost:8065/boards/public/team/team-id/shared/1/1?r=aToken
</a> </a>
<div <div
class="octo-tooltip tooltip-top" class="octo-tooltip tooltip-top"

Просмотреть файл

@@ -259,7 +259,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
viewId: match.params.viewId, viewId: match.params.viewId,
teamId: match.params.teamId, teamId: match.params.teamId,
}) })
shareUrl.pathname = newPath shareUrl.pathname = `/boards/public${newPath}`
const boardPath = generatePath('/team/:teamId/:boardId/:viewId', { const boardPath = generatePath('/team/:teamId/:boardId/:viewId', {
boardId: match.params.boardId, boardId: match.params.boardId,

Просмотреть файл

@@ -21,10 +21,12 @@ jest.mock('react-router-dom', () => {
...originalModule, ...originalModule,
useRouteMatch: jest.fn(() => { useRouteMatch: jest.fn(() => {
return { return {
teamId: 'team1', params: {
boardId: 'boardId1', teamId: 'team1',
viewId: 'viewId1', boardId: 'boardId1',
cardId: 'cardId1', viewId: 'viewId1',
cardId: 'cardId1',
},
} }
}), }),
} }

Просмотреть файл

@@ -3,7 +3,7 @@
import React, {useCallback} from 'react' import React, {useCallback} from 'react'
import {FormattedMessage} from 'react-intl' import {FormattedMessage} from 'react-intl'
import {generatePath, useRouteMatch, useHistory} from 'react-router-dom' import {generatePath, useRouteMatch} from 'react-router-dom'
import Button from 'src/widgets/buttons/button' import Button from 'src/widgets/buttons/button'
import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient' import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient'
@@ -12,14 +12,17 @@ import './shareBoardLoginButton.scss'
const ShareBoardLoginButton = () => { const ShareBoardLoginButton = () => {
const match = useRouteMatch<{teamId: string, boardId: string, viewId?: string, cardId?: string}>() const match = useRouteMatch<{teamId: string, boardId: string, viewId?: string, cardId?: string}>()
const history = useHistory()
const redirectQueryParam = 'r=' + encodeURIComponent(generatePath('/:boardId?/:viewId?/:cardId?', match.params)) // Mattermost login doesn't respect the redirect query parameter
const loginPath = '/login?' + redirectQueryParam // if the user is already logged in, so we send the user to the
// board and if they are not logged in, the webapp will take care
// of the redirection
const baseURL = window.location.href.split('/boards/public')[0]
const loginPath = `${baseURL}/${generatePath('/boards/team/:teamId/:boardId?/:viewId?/:cardId?', match.params)}`
const onLoginClick = useCallback(() => { const onLoginClick = useCallback(() => {
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardLogin) TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardLogin)
history.push(loginPath) location.assign(loginPath)
}, []) }, [])
return ( return (

Просмотреть файл

@@ -52,19 +52,22 @@ const TeamPermissionsRow = (): JSX.Element => {
} }
} }
let currentRoleName = intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'}) let currentRoleName = intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})
if (board.type === BoardTypeOpen && board.minimumRole === MemberRole.Admin) { if (board.type === BoardTypeOpen) {
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'}) currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})
} else if (board.type === BoardTypeOpen && board.minimumRole === MemberRole.Editor) { if (board.minimumRole === MemberRole.Editor) {
if (board.isTemplate) { if (board.isTemplate) {
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})
} else {
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})
}
} else if (board.minimumRole === MemberRole.Commenter) {
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})
} else if (board.minimumRole === MemberRole.Viewer) {
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'}) currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})
} else {
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})
} }
} else if (board.type === BoardTypeOpen && board.minimumRole === MemberRole.Commenter) { } else {
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'}) currentRoleName = intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'})
} else if (board.type === BoardTypeOpen && board.minimumRole === MemberRole.Viewer) {
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})
} }
const confirmationDialog = ( const confirmationDialog = (

Просмотреть файл

@@ -59,9 +59,12 @@ const ViewHeader = (props: Props) => {
const [showFilter, setShowFilter] = useState(false) const [showFilter, setShowFilter] = useState(false)
const [lockFilterOnClose, setLockFilterOnClose] = useState(false) const [lockFilterOnClose, setLockFilterOnClose] = useState(false)
const intl = useIntl() const intl = useIntl()
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
const {board, activeView, views, groupByProperty, cards, dateDisplayProperty} = props const {board, activeView, views, groupByProperty, cards, dateDisplayProperty} = props
let readonly = props.readonly
if(!readonly){
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
}
const withGroupBy = activeView.fields.viewType === 'board' || activeView.fields.viewType === 'table' const withGroupBy = activeView.fields.viewType === 'board' || activeView.fields.viewType === 'table'
const withDisplayBy = activeView.fields.viewType === 'calendar' const withDisplayBy = activeView.fields.viewType === 'calendar'
@@ -137,7 +140,7 @@ const ViewHeader = (props: Props) => {
}} }}
onChange={setViewTitle} onChange={setViewTitle}
saveOnEsc={true} saveOnEsc={true}
readonly={props.readonly || !canEditBoardProperties} readonly={readonly}
spellCheck={true} spellCheck={true}
autoExpand={false} autoExpand={false}
/> />
@@ -148,7 +151,7 @@ const ViewHeader = (props: Props) => {
board={board} board={board}
activeView={activeView} activeView={activeView}
views={views} views={views}
readonly={props.readonly || !canEditBoardProperties} readonly={readonly}
allowCreateView={allowCreateView} allowCreateView={allowCreateView}
/> />
</MenuWrapper> </MenuWrapper>
@@ -159,7 +162,7 @@ const ViewHeader = (props: Props) => {
<div className='octo-spacer'/> <div className='octo-spacer'/>
{!props.readonly && canEditBoardProperties && {!readonly &&
<> <>
{/* Card properties */} {/* Card properties */}

Просмотреть файл

@@ -34,9 +34,11 @@ const ViewTitle = (props: Props) => {
}, [board.id, board.icon]) }, [board.id, board.icon])
const onShowDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), true), [board.id, board.showDescription]) const onShowDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), true), [board.id, board.showDescription])
const onHideDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), false), [board.id, board.showDescription]) const onHideDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), false), [board.id, board.showDescription])
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
const readonly = props.readonly || !canEditBoardProperties let readonly = props.readonly
if(!readonly){
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
}
const intl = useIntl() const intl = useIntl()

Просмотреть файл

@@ -114,6 +114,7 @@ jest.mock('react-router-dom', () => {
useRouteMatch: jest.fn(() => { useRouteMatch: jest.fn(() => {
return { return {
params: { params: {
teamId: board.teamId,
boardId: board.id, boardId: board.id,
viewId: activeView.id, viewId: activeView.id,
}, },

Просмотреть файл

@@ -12,13 +12,12 @@ export const useHasPermissions = (teamId: string, boardId: string, permissions:
return false return false
} }
const member = useAppSelector(getMyBoardMembership(boardId))
const board = useAppSelector(getBoard(boardId)) const board = useAppSelector(getBoard(boardId))
if (!board) { if (!board) {
return false return false
} }
const member = useAppSelector(getMyBoardMembership(boardId))
if (!member) { if (!member) {
return false return false
} }

Просмотреть файл

@@ -28,6 +28,7 @@ windowAny.baseURL = '/plugins/boards'
windowAny.frontendBaseURL = '/boards' windowAny.frontendBaseURL = '/boards'
import App from 'src/app' import App from 'src/app'
import PublicApp, {publicBaseURL} from 'src/public/app'
import store from 'src/store' import store from 'src/store'
import WithWebSockets from 'src/components/withWebSockets' import WithWebSockets from 'src/components/withWebSockets'
@@ -175,10 +176,44 @@ const MainApp = (props: Props) => {
) )
} }
const PublicMainApp = () => {
useEffect(() => {
document.body.classList.add('focalboard-body')
const root = document.getElementById('root')
if (root) {
while (root.firstElementChild) {
if( root.firstElementChild.id === 'focalboard-app'){
break
}
root.removeChild(root.firstElementChild)
}
root.classList.add('focalboard-plugin-root')
}
return () => {
document.body.classList.remove('focalboard-body')
if (root) {
root.classList.remove('focalboard-plugin-root')
}
}
}, [])
return (
<ErrorBoundary>
<ReduxProvider store={store}>
<div id='focalboard-app'>
<PublicApp />
</div>
<div id='focalboard-root-portal' />
</ReduxProvider>
</ErrorBoundary>
)
}
const HeaderComponent = () => { const HeaderComponent = () => {
return ( return (
<ErrorBoundary> <ErrorBoundary>
<GlobalHeader history={browserHistory}/> <GlobalHeader history={browserHistory} />
</ErrorBoundary> </ErrorBoundary>
) )
} }
@@ -243,8 +278,6 @@ export default class Plugin {
} }
}) })
const initCurrentChannelId = mmStore.getState().entities.channels.currentChannelId const initCurrentChannelId = mmStore.getState().entities.channels.currentChannelId
const initCurrentChannel = mmStore.getState().entities.channels.channels[initCurrentChannelId] const initCurrentChannel = mmStore.getState().entities.channels.channels[initCurrentChannelId]
let lastViewedChannelId = initCurrentChannelId let lastViewedChannelId = initCurrentChannelId
@@ -274,7 +307,9 @@ export default class Plugin {
prevTeamId = currentTeamId prevTeamId = currentTeamId
store.dispatch(setTeam(currentTeamId)) store.dispatch(setTeam(currentTeamId))
octoClient.teamId = currentTeamId octoClient.teamId = currentTeamId
store.dispatch(initialLoad()) if(!window.location.pathname.includes(publicBaseURL())){
store.dispatch(initialLoad())
}
} }
if (currentTeamId && currentTeamId !== prevTeamId) { if (currentTeamId && currentTeamId !== prevTeamId) {
@@ -336,6 +371,9 @@ export default class Plugin {
HeaderComponent, HeaderComponent,
() => null, () => null,
true, true,
true,
true,
PublicMainApp,
) )
const goToFocalboardTemplate = () => { const goToFocalboardTemplate = () => {

Просмотреть файл

@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`properties/number should match snapshot for number with empty value 1`] = `
<div>
<input
class="Editable octo-propertyvalue"
placeholder=""
style="width: 100%;"
title=""
value=""
/>
</div>
`;

Просмотреть файл

@@ -0,0 +1,70 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {ComponentProps} from 'react'
import {screen} from '@testing-library/react'
import {mocked} from 'jest-mock'
import {setup, wrapIntl} from 'src/testUtils'
import {TestBlockFactory} from 'src/test/testBlockFactory'
import mutator from 'src/mutator'
import {Board, IPropertyTemplate} from 'src/blocks/board'
import {Card} from 'src/blocks/card'
import NumberProperty from './property'
import NumberEditor from './number'
jest.mock('src/components/flashMessages')
jest.mock('src/mutator')
const mockedMutator = mocked(mutator)
describe('properties/number', () => {
let board: Board
let card: Card
let propertyTemplate: IPropertyTemplate
let baseProps: ComponentProps<typeof NumberEditor>
beforeEach(() => {
board = TestBlockFactory.createBoard()
card = TestBlockFactory.createCard()
propertyTemplate = board.cardProperties[0]
baseProps = {
property: new NumberProperty(),
card,
board,
propertyTemplate,
propertyValue: '',
readOnly: false,
showEmptyPlaceholder: false,
}
})
it('should match snapshot for number with empty value', () => {
const {container} = setup(
wrapIntl((
<NumberEditor
{...baseProps}
/>
))
)
expect(container).toMatchSnapshot()
})
it('should fire change event when valid number value is entered', async () => {
const {user} = setup(
wrapIntl(
<NumberEditor
{...baseProps}
/>
)
)
const value = '42'
const input = screen.getByRole('textbox')
await user.type(input, `${value}{Enter}`)
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, `${value}`)
})
})

Просмотреть файл

@@ -10,7 +10,7 @@ const Number = (props: PropertyProps): JSX.Element => {
return ( return (
<BaseTextEditor <BaseTextEditor
{...props} {...props}
validator={() => !isNaN(parseInt(props.propertyValue as string, 10))} validator={() => props.propertyValue === '' || !isNaN(parseInt(props.propertyValue as string, 10))}
/> />
) )
} }

61
webapp/boards/src/public/app.tsx Обычный файл
Просмотреть файл

@@ -0,0 +1,61 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import {Router, Switch} from 'react-router-dom'
import {IntlProvider} from 'react-intl'
import {DndProvider} from 'react-dnd'
import {HTML5Backend} from 'react-dnd-html5-backend'
import {TouchBackend} from 'react-dnd-touch-backend'
import {createBrowserHistory} from 'history'
import BoardPage from 'src/pages/boardPage/boardPage'
import FBRoute from 'src/route'
import {getMessages} from 'src/i18n'
import FlashMessages from 'src/components/flashMessages'
import NewVersionBanner from 'src/components/newVersionBanner'
import {Utils} from 'src/utils'
import {getLanguage} from 'src/store/language'
import {useAppSelector} from 'src/store/hooks'
export const publicBaseURL = () => {
return Utils.getFrontendBaseURL() + '/public'
}
const PublicRouter = () => {
const history = createBrowserHistory({basename: publicBaseURL()})
return (
<Router history={history}>
<Switch>
<FBRoute path={['/team/:teamId/shared/:boardId?/:viewId?/:cardId?', '/shared/:boardId?/:viewId?/:cardId?']}>
<BoardPage readonly={true}/>
</FBRoute>
</Switch>
</Router>
)
}
const PublicApp = (): JSX.Element => {
const language = useAppSelector<string>(getLanguage)
return (
<IntlProvider
locale={language.split(/[_]/)[0]}
messages={getMessages(language)}
>
<DndProvider backend={Utils.isMobile() ? TouchBackend : HTML5Backend}>
<FlashMessages milliseconds={2000}/>
<div id='frame'>
<div id='main'>
<NewVersionBanner/>
<PublicRouter/>
</div>
</div>
</DndProvider>
</IntlProvider>
)
}
export default React.memo(PublicApp)

Просмотреть файл

@@ -14,7 +14,9 @@ export interface PluginRegistry {
registerCustomRoute(route: string, component: ReactResolvable): any registerCustomRoute(route: string, component: ReactResolvable): any
registerProductRoute(route: string, component: ReactResolvable): any registerProductRoute(route: string, component: ReactResolvable): any
unregisterComponent(componentId: string): any unregisterComponent(componentId: string): any
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string, mainComponent: ReactResolvable, headerCentreComponent: ReactResolvable, headerRightComponent: ReactResolvable, showTeamSidebar: boolean): any registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string,
mainComponent: ReactResolvable, headerCentreComponent: ReactResolvable, headerRightComponent: ReactResolvable,
showTeamSidebar: boolean, showAppBar: boolean, wrapped: boolean, publicComponent: ReactResolvable | null): any
registerPostWillRenderEmbedComponent(match: (embed: {type: string, data: any}) => void, component: any, toggleable: boolean): any registerPostWillRenderEmbedComponent(match: (embed: {type: string, data: any}) => void, component: any, toggleable: boolean): any
registerWebSocketEventHandler(event: string, handler: (e: any) => void): any registerWebSocketEventHandler(event: string, handler: (e: any) => void): any
unregisterWebSocketEventHandler(event: string): any unregisterWebSocketEventHandler(event: string): any

Просмотреть файл

@@ -5,7 +5,7 @@
"plugin:react-hooks/recommended" "plugin:react-hooks/recommended"
], ],
"plugins": [ "plugins": [
"@babel/eslint-plugin", "@babel",
"mattermost", "mattermost",
"import", "import",
"no-only-tests", "no-only-tests",

Просмотреть файл

@@ -7,7 +7,7 @@
"private": true, "private": true,
"engines": { "engines": {
"npm": ">=7.0.0", "npm": ">=7.0.0",
"node": ">=16.4.0" "node": ">=16.10.0"
}, },
"dependencies": { "dependencies": {
"@floating-ui/react-dom": "1.0.0", "@floating-ui/react-dom": "1.0.0",
@@ -153,8 +153,8 @@
"@types/styled-components": "5.1.24", "@types/styled-components": "5.1.24",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/webpack-env": "1.18.0", "@types/webpack-env": "1.18.0",
"@typescript-eslint/eslint-plugin": "5.41.0", "@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.41.0", "@typescript-eslint/parser": "5.57.1",
"babel-loader": "8.2.3", "babel-loader": "8.2.3",
"babel-plugin-lodash": "3.3.4", "babel-plugin-lodash": "3.3.4",
"babel-plugin-styled-components": "1.12.0", "babel-plugin-styled-components": "1.12.0",
@@ -167,14 +167,13 @@
"enzyme": "3.11.0", "enzyme": "3.11.0",
"enzyme-adapter-react-17-updated": "1.0.2", "enzyme-adapter-react-17-updated": "1.0.2",
"enzyme-to-json": "3.6.2", "enzyme-to-json": "3.6.2",
"eslint": "7.32.0",
"eslint-import-resolver-webpack": "0.13.2", "eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-formatjs": "4.3.4", "eslint-plugin-formatjs": "4.9.1",
"eslint-plugin-header": "3.1.1", "eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.23.4", "eslint-plugin-import": "2.27.5",
"eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#5b0c972eacf19286e4c66221b39113bf8728a99e", "eslint-plugin-mattermost": "github:mattermost/eslint-plugin-mattermost#5b0c972eacf19286e4c66221b39113bf8728a99e",
"eslint-plugin-no-only-tests": "2.6.0", "eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-react": "7.31.10", "eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"external-remotes-plugin": "1.0.0", "external-remotes-plugin": "1.0.0",
"file-loader": "6.2.0", "file-loader": "6.2.0",

Просмотреть файл

@@ -49,7 +49,7 @@ export default class AboutBuildModal extends React.PureComponent<Props, State> {
doHide = () => { doHide = () => {
this.setState({show: false}); this.setState({show: false});
} };
render() { render() {
const config = this.props.config; const config = this.props.config;

Просмотреть файл

@@ -32,11 +32,11 @@ export default class AccessHistoryModal extends React.PureComponent<Props, State
public onShow = () => { // public for testing public onShow = () => { // public for testing
this.props.actions.getUserAudits(this.props.currentUserId, 0, 200); this.props.actions.getUserAudits(this.props.currentUserId, 0, 200);
} };
public onHide = () => { // public for testing public onHide = () => { // public for testing
this.setState({show: false}); this.setState({show: false});
} };
public componentDidMount() { public componentDidMount() {
this.onShow(); this.onShow();

Просмотреть файл

@@ -23,7 +23,7 @@ export default class Accordion extends React.PureComponent<Props, State> {
setExpanded = (expandedKey: string) => { setExpanded = (expandedKey: string) => {
this.setState({expandedKey}); this.setState({expandedKey});
} };
render() { render() {
return ( return (

Просмотреть файл

@@ -92,7 +92,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
appBindings: [], appBindings: [],
location: Locations.CENTER, location: Locations.CENTER,
pluginMenuItems: [], pluginMenuItems: [],
} };
private buttonRef: React.RefObject<HTMLButtonElement>; private buttonRef: React.RefObject<HTMLButtonElement>;
constructor(props: Props) { constructor(props: Props) {
@@ -115,7 +115,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
defaultMessage='Message actions' defaultMessage='Message actions'
/> />
</Tooltip> </Tooltip>
) );
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: Props) {
if (this.props.isMenuOpen && !prevProps.isMenuOpen) { if (this.props.isMenuOpen && !prevProps.isMenuOpen) {
@@ -137,7 +137,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
this.setState({appBindings: data}); this.setState({appBindings: data});
}); });
} }
} };
handleOpenMarketplace = (): void => { handleOpenMarketplace = (): void => {
const openedFrom: OpenedFromType = 'actions_menu'; const openedFrom: OpenedFromType = 'actions_menu';
@@ -197,7 +197,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
this.props.actions.postEphemeralCallResponseForPost(callResp, errorMessage, post); this.props.actions.postEphemeralCallResponseForPost(callResp, errorMessage, post);
} }
} }
} };
visitMarketplaceTip(): React.ReactElement { visitMarketplaceTip(): React.ReactElement {
return ( return (
@@ -263,7 +263,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
this.setState({ this.setState({
openUp: (spaceOnTop > spaceOnBottom), openUp: (spaceOnTop > spaceOnBottom),
}); });
} };
render(): React.ReactNode { render(): React.ReactNode {
const isSystemMessage = PostUtils.isSystemMessage(this.props.post); const isSystemMessage = PostUtils.isSystemMessage(this.props.post);

Просмотреть файл

@@ -53,7 +53,7 @@ type State = {
export default class ActivityLogModal extends React.PureComponent<Props, State> { export default class ActivityLogModal extends React.PureComponent<Props, State> {
static propTypes = { static propTypes = {
} };
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@@ -73,15 +73,15 @@ export default class ActivityLogModal extends React.PureComponent<Props, State>
this.props.actions.revokeSession(this.props.currentUserId, altId).then(() => { this.props.actions.revokeSession(this.props.currentUserId, altId).then(() => {
this.props.actions.getSessions(this.props.currentUserId); this.props.actions.getSessions(this.props.currentUserId);
}); });
} };
onShow = () => { onShow = () => {
this.props.actions.getSessions(this.props.currentUserId); this.props.actions.getSessions(this.props.currentUserId);
} };
onHide = () => { onHide = () => {
this.setState({show: false}); this.setState({show: false});
} };
componentDidMount() { componentDidMount() {
this.onShow(); this.onShow();

Просмотреть файл

@@ -56,11 +56,11 @@ export default class ActivityLog extends React.PureComponent<Props, State> {
handleMoreInfo = (): void => { handleMoreInfo = (): void => {
this.setState({moreInfo: true}); this.setState({moreInfo: true});
} };
submitRevoke = (e: React.MouseEvent): void => { submitRevoke = (e: React.MouseEvent): void => {
this.props.submitRevoke(this.props.currentSession.id, e); this.props.submitRevoke(this.props.currentSession.id, e);
} };
isMobileSession = (session: Session): boolean => { isMobileSession = (session: Session): boolean => {
return Boolean(session.device_id && (session.device_id.includes('apple') || session.device_id.includes('android'))); return Boolean(session.device_id && (session.device_id.includes('apple') || session.device_id.includes('android')));

Просмотреть файл

@@ -110,7 +110,7 @@ export default class AddGroupsToChannelModal extends React.PureComponent<Props,
handleHide = () => { handleHide = () => {
this.props.actions.setModalSearchTerm(''); this.props.actions.setModalSearchTerm('');
this.setState({show: false}); this.setState({show: false});
} };
handleResponse = (err?: ServerError) => { handleResponse = (err?: ServerError) => {
let addError = null; let addError = null;
@@ -122,7 +122,7 @@ export default class AddGroupsToChannelModal extends React.PureComponent<Props,
saving: false, saving: false,
addError, addError,
}); });
} };
handleSubmit = async () => { handleSubmit = async () => {
const groupIDs = this.state.values.map((v) => v.id); const groupIDs = this.state.values.map((v) => v.id);
@@ -147,7 +147,7 @@ export default class AddGroupsToChannelModal extends React.PureComponent<Props,
this.handleHide(); this.handleHide();
} }
})); }));
} };
addValue = (value: GroupValue) => { addValue = (value: GroupValue) => {
const values = Object.assign([], this.state.values); const values = Object.assign([], this.state.values);
@@ -157,13 +157,13 @@ export default class AddGroupsToChannelModal extends React.PureComponent<Props,
} }
this.setState({values}); this.setState({values});
} };
setGroupsLoadingState = (loadingState: boolean) => { setGroupsLoadingState = (loadingState: boolean) => {
this.setState({ this.setState({
loadingGroups: loadingState, loadingGroups: loadingState,
}); });
} };
handlePageChange = (page: number, prevPage: number) => { handlePageChange = (page: number, prevPage: number) => {
if (page > prevPage) { if (page > prevPage) {
@@ -172,15 +172,15 @@ export default class AddGroupsToChannelModal extends React.PureComponent<Props,
this.setGroupsLoadingState(false); this.setGroupsLoadingState(false);
}); });
} }
} };
handleDelete = (values: GroupValue[]) => { handleDelete = (values: GroupValue[]) => {
this.setState({values}); this.setState({values});
} };
search = (term: string) => { search = (term: string) => {
this.props.actions.setModalSearchTerm(term); this.props.actions.setModalSearchTerm(term);
} };
renderOption = (option: GroupValue, isSelected: boolean, onAdd: (value: GroupValue) => void, onMouseMove?: (value: GroupValue) => void) => { renderOption = (option: GroupValue, isSelected: boolean, onAdd: (value: GroupValue) => void, onMouseMove?: (value: GroupValue) => void) => {
const rowSelected = isSelected ? 'more-modal__row--selected' : ''; const rowSelected = isSelected ? 'more-modal__row--selected' : '';
@@ -222,7 +222,7 @@ export default class AddGroupsToChannelModal extends React.PureComponent<Props,
</div> </div>
</div> </div>
); );
} };
renderValue(props: { data: Partial<Value> }) { renderValue(props: { data: Partial<Value> }) {
return props.data.display_name; return props.data.display_name;

Просмотреть файл

@@ -105,12 +105,12 @@ export default class AddGroupsToTeamModal extends React.PureComponent<Props, Sta
public handleHide = () => { public handleHide = () => {
this.props.actions.setModalSearchTerm(''); this.props.actions.setModalSearchTerm('');
this.setState({show: false}); this.setState({show: false});
} };
// public for tests // public for tests
public handleExit = (): void => { public handleExit = (): void => {
this.props.onExited(); this.props.onExited();
} };
// public for tests // public for tests
public handleResponse = (err?: Error): void => { public handleResponse = (err?: Error): void => {
@@ -123,7 +123,7 @@ export default class AddGroupsToTeamModal extends React.PureComponent<Props, Sta
saving: false, saving: false,
addError, addError,
}); });
} };
// public for tests // public for tests
public handleSubmit = async () => { public handleSubmit = async () => {
@@ -148,7 +148,7 @@ export default class AddGroupsToTeamModal extends React.PureComponent<Props, Sta
this.handleHide(); this.handleHide();
} }
})); }));
} };
// public for tests // public for tests
public addValue = (value: GroupValue): void => { public addValue = (value: GroupValue): void => {
@@ -159,13 +159,13 @@ export default class AddGroupsToTeamModal extends React.PureComponent<Props, Sta
} }
this.setState({values}); this.setState({values});
} };
private setGroupsLoadingState = (loadingState: boolean) => { private setGroupsLoadingState = (loadingState: boolean) => {
this.setState({ this.setState({
loadingGroups: loadingState, loadingGroups: loadingState,
}); });
} };
// public for tests // public for tests
public handlePageChange = (page: number, prevPage: number): void => { public handlePageChange = (page: number, prevPage: number): void => {
@@ -175,7 +175,7 @@ export default class AddGroupsToTeamModal extends React.PureComponent<Props, Sta
this.setGroupsLoadingState(false); this.setGroupsLoadingState(false);
}); });
} }
} };
// public for tests // public for tests
public handleDelete = (values: GroupValue[]): void => this.setState({values}); public handleDelete = (values: GroupValue[]): void => this.setState({values});
@@ -224,7 +224,7 @@ export default class AddGroupsToTeamModal extends React.PureComponent<Props, Sta
</div> </div>
</div> </div>
); );
} };
// public for tests // public for tests
public renderValue = (props: { data: Value }): string | undefined => props.data.display_name; public renderValue = (props: { data: Value }): string | undefined => props.data.display_name;

Просмотреть файл

@@ -114,7 +114,7 @@ export default class AddUserToChannelModal extends React.PureComponent<Props, St
enableChannelProvider = () => { enableChannelProvider = () => {
this.suggestionProviders[0].disableDispatches = false; this.suggestionProviders[0].disableDispatches = false;
} };
focusTextbox = () => { focusTextbox = () => {
if (this.channelSearchBox == null) { if (this.channelSearchBox == null) {
@@ -126,30 +126,30 @@ export default class AddUserToChannelModal extends React.PureComponent<Props, St
textbox.focus(); textbox.focus();
placeCaretAtEnd(textbox); placeCaretAtEnd(textbox);
} }
} };
onInputChange = (e: ChangeEvent<HTMLInputElement>) => { onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({text: e.target.value, selectedChannelId: null}); this.setState({text: e.target.value, selectedChannelId: null});
} };
onHide = () => { onHide = () => {
this.setState({show: false}); this.setState({show: false});
} };
onExited = () => { onExited = () => {
this.props.onExited(); this.props.onExited();
} };
setSearchBoxRef = (input: SuggestionBoxComponent) => { setSearchBoxRef = (input: SuggestionBoxComponent) => {
this.channelSearchBox = input; this.channelSearchBox = input;
this.focusTextbox(); this.focusTextbox();
} };
handleSubmitError = (error: {message: string}) => { handleSubmitError = (error: {message: string}) => {
if (error) { if (error) {
this.setState({submitError: error.message, saving: false}); this.setState({submitError: error.message, saving: false});
} }
} };
didSelectChannel = (selection: {channel: Channel}) => { didSelectChannel = (selection: {channel: Channel}) => {
const channel = selection.channel; const channel = selection.channel;
@@ -165,7 +165,7 @@ export default class AddUserToChannelModal extends React.PureComponent<Props, St
this.props.actions.getChannelMember(channel.id, userId).then(() => { this.props.actions.getChannelMember(channel.id, userId).then(() => {
this.setState({checkingForMembership: false}); this.setState({checkingForMembership: false});
}); });
} };
handleSubmit = (e: FormEvent) => { handleSubmit = (e: FormEvent) => {
if (e && e.preventDefault) { if (e && e.preventDefault) {
@@ -192,7 +192,7 @@ export default class AddUserToChannelModal extends React.PureComponent<Props, St
this.onHide(); this.onHide();
} }
}); });
} };
isUserMemberOfChannel = (channelId: string | null) => { isUserMemberOfChannel = (channelId: string | null) => {
const user = this.props.user; const user = this.props.user;
@@ -207,7 +207,7 @@ export default class AddUserToChannelModal extends React.PureComponent<Props, St
} }
return Boolean(memberships[channelId][user.id]); return Boolean(memberships[channelId][user.id]);
} };
render() { render() {
const user = this.props.user; const user = this.props.user;

Просмотреть файл

@@ -173,7 +173,7 @@ export default class AddUserToGroupMultiSelect extends React.PureComponent<Props
return ''; return '';
} }
return option.username; return option.username;
} };
renderOption = (option: UserProfileValue, isSelected: boolean, onAdd: (user: UserProfileValue) => void, onMouseMove: (user: UserProfileValue) => void) => { renderOption = (option: UserProfileValue, isSelected: boolean, onAdd: (user: UserProfileValue) => void, onMouseMove: (user: UserProfileValue) => void) => {
return ( return (
@@ -241,5 +241,5 @@ export default class AddUserToGroupMultiSelect extends React.PureComponent<Props
numRemainingText={numRemainingText} numRemainingText={numRemainingText}
/> />
); );
} };
} }

Просмотреть файл

@@ -78,11 +78,11 @@ export default class AddUsersToTeamModal extends React.PureComponent<Props, Stat
public componentDidMount = async () => { public componentDidMount = async () => {
await this.props.actions.getProfilesNotInTeam(this.props.team.id, false, 0, USERS_PER_PAGE * 2); await this.props.actions.getProfilesNotInTeam(this.props.team.id, false, 0, USERS_PER_PAGE * 2);
this.setUsersLoadingState(false); this.setUsersLoadingState(false);
} };
private setUsersLoadingState = (loading: boolean) => { private setUsersLoadingState = (loading: boolean) => {
this.setState({loading}); this.setState({loading});
} };
public search = async (term: string) => { public search = async (term: string) => {
this.setUsersLoadingState(true); this.setUsersLoadingState(true);
@@ -95,17 +95,17 @@ export default class AddUsersToTeamModal extends React.PureComponent<Props, Stat
await this.props.actions.getProfilesNotInTeam(this.props.team.id, false, 0, USERS_PER_PAGE * 2); await this.props.actions.getProfilesNotInTeam(this.props.team.id, false, 0, USERS_PER_PAGE * 2);
} }
this.setState({loading: false, searchResults, search}); this.setState({loading: false, searchResults, search});
} };
public handleHide = () => { public handleHide = () => {
this.setState({show: false}); this.setState({show: false});
} };
private handleExit = () => { private handleExit = () => {
if (this.props.onExited) { if (this.props.onExited) {
this.props.onExited(); this.props.onExited();
} }
} };
private renderOption = (option: UserProfileValue, isSelected: boolean, onAdd: (user: UserProfileValue) => void, onMouseMove: (user: UserProfileValue) => void) => { private renderOption = (option: UserProfileValue, isSelected: boolean, onAdd: (user: UserProfileValue) => void, onMouseMove: (user: UserProfileValue) => void) => {
let rowSelected = ''; let rowSelected = '';
@@ -139,15 +139,15 @@ export default class AddUsersToTeamModal extends React.PureComponent<Props, Stat
</div> </div>
</div> </div>
); );
} };
private renderValue = (value: { data: UserProfileValue }): string => { private renderValue = (value: { data: UserProfileValue }): string => {
return value.data?.username || ''; return value.data?.username || '';
} };
private renderAriaLabel = (option: UserProfileValue): string => { private renderAriaLabel = (option: UserProfileValue): string => {
return option?.username || ''; return option?.username || '';
} };
private handleAdd = (value: UserProfileValue) => { private handleAdd = (value: UserProfileValue) => {
const values: UserProfileValue[] = [...this.state.values]; const values: UserProfileValue[] = [...this.state.values];
@@ -155,11 +155,11 @@ export default class AddUsersToTeamModal extends React.PureComponent<Props, Stat
values.push(value); values.push(value);
} }
this.setState({values}); this.setState({values});
} };
private handleDelete = (values: UserProfileValue[]) => { private handleDelete = (values: UserProfileValue[]) => {
this.setState({values}); this.setState({values});
} };
private handlePageChange = (page: number, prevPage: number) => { private handlePageChange = (page: number, prevPage: number) => {
if (page > prevPage) { if (page > prevPage) {
@@ -173,7 +173,7 @@ export default class AddUsersToTeamModal extends React.PureComponent<Props, Stat
private handleSubmit = () => { private handleSubmit = () => {
this.props.onAddCallback(this.state.values); this.props.onAddCallback(this.state.values);
this.handleHide(); this.handleHide();
} };
public render = (): JSX.Element => { public render = (): JSX.Element => {
const numRemainingText = ( const numRemainingText = (
@@ -259,5 +259,5 @@ export default class AddUsersToTeamModal extends React.PureComponent<Props, Stat
</Modal.Body> </Modal.Body>
</Modal> </Modal>
); );
} };
} }

Просмотреть файл

@@ -93,7 +93,7 @@ export default class AdminConsole extends React.PureComponent<Props, State> {
private onFilterChange = (filter: string) => { private onFilterChange = (filter: string) => {
this.setState({filter}); this.setState({filter});
} };
private mainRolesLoaded(roles: Record<string, Role>) { private mainRolesLoaded(roles: Record<string, Role>) {
return ( return (
@@ -186,7 +186,7 @@ export default class AdminConsole extends React.PureComponent<Props, State> {
{<Redirect to={`${this.props.match.url}/${defaultUrl}`}/>} {<Redirect to={`${this.props.match.url}/${defaultUrl}`}/>}
</Switch> </Switch>
); );
} };
public render(): JSX.Element | null { public render(): JSX.Element | null {
const { const {

Просмотреть файл

@@ -5746,7 +5746,6 @@ const AdminDefinition = {
sectionTitle: t('admin.sidebar.products'), sectionTitle: t('admin.sidebar.products'),
sectionTitleDefault: 'Products', sectionTitleDefault: 'Products',
isHidden: it.any( isHidden: it.any(
it.configIsFalse('FeatureFlags', 'BoardsProduct'),
it.not(it.userHasReadPermissionOnSomeResources(RESOURCE_KEYS.PRODUCTS)), it.not(it.userHasReadPermissionOnSomeResources(RESOURCE_KEYS.PRODUCTS)),
), ),
boards: { boards: {

Просмотреть файл

@@ -69,7 +69,7 @@ export default abstract class AdminSettings <Props extends BaseProps, State exte
private closeTooltip = () => { private closeTooltip = () => {
this.setState({errorTooltip: false}); this.setState({errorTooltip: false});
} };
private openTooltip = (e: React.MouseEvent) => { private openTooltip = (e: React.MouseEvent) => {
const elm: HTMLElement|null = e.currentTarget.querySelector('.control-label'); const elm: HTMLElement|null = e.currentTarget.querySelector('.control-label');
@@ -77,7 +77,7 @@ export default abstract class AdminSettings <Props extends BaseProps, State exte
const isElipsis = elm.offsetWidth < elm.scrollWidth; const isElipsis = elm.offsetWidth < elm.scrollWidth;
this.setState({errorTooltip: isElipsis}); this.setState({errorTooltip: isElipsis});
} }
} };
protected handleChange = (id: string, value: boolean) => { protected handleChange = (id: string, value: boolean) => {
this.setState((prevState) => ({ this.setState((prevState) => ({
@@ -95,7 +95,7 @@ export default abstract class AdminSettings <Props extends BaseProps, State exte
e.preventDefault(); e.preventDefault();
this.doSubmit(); this.doSubmit();
} };
protected doSubmit = async (callback?: () => void) => { protected doSubmit = async (callback?: () => void) => {
this.setState({ this.setState({

Просмотреть файл

@@ -1072,6 +1072,37 @@ exports[`components/AdminSidebar should match snapshot 1`] = `
title="Plugin 0" title="Plugin 0"
/> />
</AdminSidebarCategory> </AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory <AdminSidebarCategory
definitionKey="integrations" definitionKey="integrations"
icon={ icon={
@@ -1775,6 +1806,37 @@ exports[`components/AdminSidebar should match snapshot with workspace optimizati
title="Plugin 0" title="Plugin 0"
/> />
</AdminSidebarCategory> </AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory <AdminSidebarCategory
definitionKey="integrations" definitionKey="integrations"
icon={ icon={
@@ -2533,6 +2595,37 @@ exports[`components/AdminSidebar should match snapshot, not prevent the console
title="Plugin 0" title="Plugin 0"
/> />
</AdminSidebarCategory> </AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory <AdminSidebarCategory
definitionKey="integrations" definitionKey="integrations"
icon={ icon={
@@ -3236,6 +3329,37 @@ exports[`components/AdminSidebar should match snapshot, render plugins without a
title="Plugin 0" title="Plugin 0"
/> />
</AdminSidebarCategory> </AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory <AdminSidebarCategory
definitionKey="integrations" definitionKey="integrations"
icon={ icon={
@@ -4038,6 +4162,37 @@ exports[`components/AdminSidebar should match snapshot, with license (with all f
title="Plugin 0" title="Plugin 0"
/> />
</AdminSidebarCategory> </AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory <AdminSidebarCategory
definitionKey="integrations" definitionKey="integrations"
icon={ icon={
@@ -4973,6 +5128,37 @@ exports[`components/AdminSidebar should match snapshot, with license (without an
title="Plugin 0" title="Plugin 0"
/> />
</AdminSidebarCategory> </AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory <AdminSidebarCategory
definitionKey="integrations" definitionKey="integrations"
icon={ icon={

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше