MM-53088 - remove autoshow linked board (#23783)
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
283abbe704
Коммит
887ba95cc1
@@ -675,7 +675,6 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
WysiwygEditor: false,
|
WysiwygEditor: false,
|
||||||
PeopleProduct: false,
|
PeopleProduct: false,
|
||||||
ReduceOnBoardingTaskList: false,
|
ReduceOnBoardingTaskList: false,
|
||||||
OnboardingAutoShowLinkedBoard: false,
|
|
||||||
ThreadsEverywhere: false,
|
ThreadsEverywhere: false,
|
||||||
GlobalDrafts: true,
|
GlobalDrafts: true,
|
||||||
OnboardingTourTips: true,
|
OnboardingTourTips: true,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {expect, test} from '@e2e-support/test_fixture';
|
import {expect, test} from '@e2e-support/test_fixture';
|
||||||
|
import {duration, wait} from '@e2e-support/util';
|
||||||
|
|
||||||
test('Intro to channel as regular user', async ({pw, pages, browserName, viewport}, testInfo) => {
|
test('Intro to channel as regular user', async ({pw, pages, browserName, viewport}, testInfo) => {
|
||||||
// Create and sign in a new user
|
// Create and sign in a new user
|
||||||
@@ -16,10 +17,9 @@ test('Intro to channel as regular user', async ({pw, pages, browserName, viewpor
|
|||||||
await channelsPage.toBeVisible();
|
await channelsPage.toBeVisible();
|
||||||
|
|
||||||
// Wait for Boards' bot image to be loaded
|
// Wait for Boards' bot image to be loaded
|
||||||
// await pw.shouldHaveFeatureFlag('OnboardingAutoShowLinkedBoard', true);
|
const boardsWelcomePost = await channelsPage.getFirstPost();
|
||||||
// const boardsWelcomePost = await channelsPage.getFirstPost();
|
await expect(await boardsWelcomePost.getProfileImage('boards')).toBeVisible();
|
||||||
// await expect(await boardsWelcomePost.getProfileImage('boards')).toBeVisible();
|
await wait(duration.one_sec);
|
||||||
// await wait(duration.one_sec);
|
|
||||||
|
|
||||||
// Wait for Playbooks icon to be loaded in App bar, except in iphone
|
// Wait for Playbooks icon to be loaded in App bar, except in iphone
|
||||||
if (!pw.isSmallScreen()) {
|
if (!pw.isSmallScreen()) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package app
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -18,8 +17,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
fb_model "github.com/mattermost/mattermost/server/v8/boards/model"
|
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
||||||
@@ -163,128 +160,6 @@ func (a *App) SoftDeleteAllTeamsExcept(teamID string) *model.AppError {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MM-48246 A/B test show linked boards
|
|
||||||
const preferenceName = "linked_board_created"
|
|
||||||
|
|
||||||
func (a *App) shouldCreateOnboardingLinkedBoard(c request.CTX, teamId string) bool {
|
|
||||||
ffEnabled := a.Config().FeatureFlags.OnboardingAutoShowLinkedBoard
|
|
||||||
if !ffEnabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
hasBoard, err := a.HasBoardProduct()
|
|
||||||
if err != nil {
|
|
||||||
a.Log().Error("error checking the existence of boards product: ", mlog.Err(err))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasBoard {
|
|
||||||
a.Log().Warn("board product not found")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
data, sysValErr := a.Srv().Store().System().GetByName(model.PreferenceOnboarding + "_" + preferenceName)
|
|
||||||
if sysValErr != nil {
|
|
||||||
var nfErr *store.ErrNotFound
|
|
||||||
if errors.As(sysValErr, &nfErr) { // if no board has been registered, it can create one for this team
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
a.Log().Error("cannot get the system values", mlog.Err(sysValErr))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the team list and check if the team value has been already stored, if so, no need to create a board in town square in that team
|
|
||||||
teamsList := strings.Split(data.Value, ",")
|
|
||||||
for _, team := range teamsList {
|
|
||||||
if team == teamId {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) createOnboardingLinkedBoard(c request.CTX, teamId string) (*fb_model.Board, *model.AppError) {
|
|
||||||
const defaultTemplatesTeam = "0"
|
|
||||||
|
|
||||||
// see https://github.com/mattermost/mattermost/server/v8/boards/blob/main/server/services/store/sqlstore/board.go#L302
|
|
||||||
// and https://github.com/mattermost/mattermost-server/pull/22201#discussion_r1099536430
|
|
||||||
const defaultTemplateTitle = "Welcome to Boards!"
|
|
||||||
welcomeToBoardsTemplateId := fmt.Sprintf("%x", md5.Sum([]byte(defaultTemplateTitle)))
|
|
||||||
userId := c.Session().UserId
|
|
||||||
|
|
||||||
boardServiceItf, ok := a.Srv().services[product.BoardsKey]
|
|
||||||
if !ok {
|
|
||||||
return nil, model.NewAppError("CreateBoard", "app.team.create_onboarding_linked_board.product_key_not_found", nil, "", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
boardService, typeOk := boardServiceItf.(product.BoardsService)
|
|
||||||
if !typeOk {
|
|
||||||
// boardServiceItf is NOT of type product.BoardsService
|
|
||||||
return nil, model.NewAppError("CreateBoard", "app.team.create_onboarding_linked_board.itf_not_of_type", nil, "", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
templates, err := boardService.GetTemplates(defaultTemplatesTeam, userId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("CreateBoard", "app.team.create_onboarding_linked_board.error_getting_templates", nil, "", http.StatusBadRequest).Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
channel, appErr := a.GetChannelByName(c, model.DefaultChannelName, teamId, false)
|
|
||||||
if appErr != nil {
|
|
||||||
return nil, appErr
|
|
||||||
}
|
|
||||||
|
|
||||||
var template *fb_model.Board
|
|
||||||
for _, t := range templates {
|
|
||||||
v := t.Properties["trackingTemplateId"]
|
|
||||||
if v == welcomeToBoardsTemplateId {
|
|
||||||
template = t
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if template == nil && len(templates) > 0 {
|
|
||||||
template = templates[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Duplicate board From template
|
|
||||||
boardsAndBlocks, _, err := boardService.DuplicateBoard(template.ID, userId, teamId, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.NewAppError("CreateBoard", "app.team.create_onboarding_linked_board.error_duplicating_board", nil, "", http.StatusBadRequest).Wrap(err)
|
|
||||||
}
|
|
||||||
if len(boardsAndBlocks.Boards) != 1 {
|
|
||||||
return nil, model.NewAppError("CreateBoard", "app.team.create_onboarding_linked_board.error_no_board", nil, "", http.StatusBadRequest).Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// link the board with the channel
|
|
||||||
patchedBoard, err := boardService.PatchBoard(&fb_model.BoardPatch{
|
|
||||||
ChannelID: &channel.Id,
|
|
||||||
}, boardsAndBlocks.Boards[0].ID, userId)
|
|
||||||
if err != nil && patchedBoard == nil {
|
|
||||||
return nil, model.NewAppError("CreateBoard", "app.team.create_onboarding_linked_board.error_patching_board", nil, "", http.StatusBadRequest).Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save in the system preferences that the board was already created once per team
|
|
||||||
data, sysValErr := a.Srv().Store().System().GetByName(model.PreferenceOnboarding + "_" + preferenceName)
|
|
||||||
if sysValErr != nil {
|
|
||||||
c.Logger().Error("cannot get the system preferences", mlog.Err(sysValErr))
|
|
||||||
}
|
|
||||||
|
|
||||||
teamsList := teamId
|
|
||||||
// if data is not nil, data.Value contains the list of teams where the A/B test has alredy created a channel for town square
|
|
||||||
if data != nil {
|
|
||||||
teamsList = data.Value + "," + teamId
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.Srv().Store().System().SaveOrUpdate(&model.System{
|
|
||||||
Name: model.PreferenceOnboarding + "_" + preferenceName,
|
|
||||||
Value: teamsList,
|
|
||||||
}); err != nil {
|
|
||||||
c.Logger().Warn("encountered error saving user preferences", mlog.Err(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return patchedBoard, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) CreateTeam(c request.CTX, team *model.Team) (*model.Team, *model.AppError) {
|
func (a *App) CreateTeam(c request.CTX, team *model.Team) (*model.Team, *model.AppError) {
|
||||||
rteam, err := a.ch.srv.teamService.CreateTeam(team)
|
rteam, err := a.ch.srv.teamService.CreateTeam(team)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -316,20 +191,6 @@ func (a *App) CreateTeam(c request.CTX, team *model.Team) (*model.Team, *model.A
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MM-48246 A/B test show linked boards. Create a welcome to boards linked board per user
|
|
||||||
if a.shouldCreateOnboardingLinkedBoard(c, team.Id) {
|
|
||||||
board, aErr := a.createOnboardingLinkedBoard(c, team.Id)
|
|
||||||
if aErr != nil || board == nil {
|
|
||||||
a.Log().Warn("Error creating the linked board, only team created", mlog.Err(err))
|
|
||||||
return rteam, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if board.ID != "" {
|
|
||||||
logInfo := fmt.Sprintf("Board created with id %s and associated to channel %s in team %s", board.ID, board.ChannelID, team.Id)
|
|
||||||
a.Log().Info(logInfo, mlog.Err(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rteam, nil
|
return rteam, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6523,30 +6523,6 @@
|
|||||||
"id": "app.team.clear_cache.app_error",
|
"id": "app.team.clear_cache.app_error",
|
||||||
"translation": "Error clearing team member cache"
|
"translation": "Error clearing team member cache"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "app.team.create_onboarding_linked_board.error_duplicating_board",
|
|
||||||
"translation": "error duplicating board"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.team.create_onboarding_linked_board.error_getting_templates",
|
|
||||||
"translation": "error getting boards templates"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.team.create_onboarding_linked_board.error_no_board",
|
|
||||||
"translation": "error no board created"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.team.create_onboarding_linked_board.error_patching_board",
|
|
||||||
"translation": "error while patching board"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.team.create_onboarding_linked_board.itf_not_of_type",
|
|
||||||
"translation": "interface not of expected type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "app.team.create_onboarding_linked_board.product_key_not_found",
|
|
||||||
"translation": "product key not found"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "app.team.get.find.app_error",
|
"id": "app.team.get.find.app_error",
|
||||||
"translation": "Unable to find the existing team."
|
"translation": "Unable to find the existing team."
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ type FeatureFlags struct {
|
|||||||
// A/B Test on reduced onboarding task list item
|
// A/B Test on reduced onboarding task list item
|
||||||
ReduceOnBoardingTaskList bool
|
ReduceOnBoardingTaskList bool
|
||||||
|
|
||||||
// A/B Test to control when to show onboarding linked board
|
|
||||||
OnboardingAutoShowLinkedBoard bool
|
|
||||||
|
|
||||||
ThreadsEverywhere bool
|
ThreadsEverywhere bool
|
||||||
|
|
||||||
GlobalDrafts bool
|
GlobalDrafts bool
|
||||||
@@ -94,7 +91,6 @@ func (f *FeatureFlags) SetDefaults() {
|
|||||||
f.GlobalDrafts = true
|
f.GlobalDrafts = true
|
||||||
f.DeprecateCloudFree = false
|
f.DeprecateCloudFree = false
|
||||||
f.WysiwygEditor = false
|
f.WysiwygEditor = false
|
||||||
f.OnboardingAutoShowLinkedBoard = false
|
|
||||||
f.OnboardingTourTips = true
|
f.OnboardingTourTips = true
|
||||||
f.CloudReverseTrial = false
|
f.CloudReverseTrial = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ const (
|
|||||||
PreferenceRecommendedNextSteps = "recommended_next_steps"
|
PreferenceRecommendedNextSteps = "recommended_next_steps"
|
||||||
PreferenceNameInsights = "insights_tutorial_state"
|
PreferenceNameInsights = "insights_tutorial_state"
|
||||||
|
|
||||||
// initial onboarding preferences
|
|
||||||
PreferenceOnboarding = "onboarding"
|
|
||||||
|
|
||||||
PreferenceCategoryTheme = "theme"
|
PreferenceCategoryTheme = "theme"
|
||||||
// the name for theme props is the team id
|
// the name for theme props is the team id
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import styled, {css} from 'styled-components';
|
|||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import {getShowTaskListBool} from 'selectors/onboarding';
|
import {getShowTaskListBool} from 'selectors/onboarding';
|
||||||
import {shouldShowAutoLinkedBoard} from 'selectors/plugins';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getBool,
|
getBool,
|
||||||
@@ -32,11 +31,10 @@ import CompassThemeProvider from 'components/compass_theme_provider/compass_them
|
|||||||
|
|
||||||
import {openModal} from 'actions/views/modals';
|
import {openModal} from 'actions/views/modals';
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
import {showRHSPlugin} from 'actions/views/rhs';
|
|
||||||
import {trackEvent} from 'actions/telemetry_actions';
|
import {trackEvent} from 'actions/telemetry_actions';
|
||||||
import checklistImg from 'images/onboarding-checklist.svg';
|
import checklistImg from 'images/onboarding-checklist.svg';
|
||||||
|
|
||||||
import {Preferences, RecommendedNextStepsLegacy, suitePluginIds} from 'utils/constants';
|
import {Preferences, RecommendedNextStepsLegacy} from 'utils/constants';
|
||||||
|
|
||||||
import {TaskListPopover} from './onboarding_tasklist_popover';
|
import {TaskListPopover} from './onboarding_tasklist_popover';
|
||||||
import {Task} from './onboarding_tasklist_task';
|
import {Task} from './onboarding_tasklist_task';
|
||||||
@@ -187,24 +185,11 @@ const OnBoardingTaskList = (): JSX.Element | null => {
|
|||||||
const [showTaskList, firstTimeOnboarding] = useSelector(getShowTaskListBool);
|
const [showTaskList, firstTimeOnboarding] = useSelector(getShowTaskListBool);
|
||||||
const theme = useSelector(getTheme);
|
const theme = useSelector(getTheme);
|
||||||
|
|
||||||
// a/b test auto show linked boards
|
|
||||||
const autoShowLinkedBoard = useSelector((state: GlobalState) => shouldShowAutoLinkedBoard(state));
|
|
||||||
const pluginsComponentsList = useSelector((state: GlobalState) => state.plugins.components);
|
|
||||||
|
|
||||||
const startTask = (taskName: string) => {
|
const startTask = (taskName: string) => {
|
||||||
toggleTaskList();
|
toggleTaskList();
|
||||||
handleTaskTrigger(taskName);
|
handleTaskTrigger(taskName);
|
||||||
};
|
};
|
||||||
|
|
||||||
const findRhsPluginId = (pluginId: string) => {
|
|
||||||
const rhsPlugins = pluginsComponentsList.RightHandSidebarComponent;
|
|
||||||
|
|
||||||
if (rhsPlugins.length) {
|
|
||||||
return rhsPlugins.find((plugin) => plugin.pluginId === pluginId)?.id;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initOnboardingPrefs = async () => {
|
const initOnboardingPrefs = async () => {
|
||||||
// save to preferences the show/open-task-list to true
|
// save to preferences the show/open-task-list to true
|
||||||
// also save the recomendedNextSteps-hide to true to avoid asserting to true
|
// also save the recomendedNextSteps-hide to true to avoid asserting to true
|
||||||
@@ -288,16 +273,6 @@ const OnBoardingTaskList = (): JSX.Element | null => {
|
|||||||
}];
|
}];
|
||||||
dispatch(savePreferences(currentUserId, preferences));
|
dispatch(savePreferences(currentUserId, preferences));
|
||||||
trackEvent(OnboardingTaskCategory, open ? OnboardingTaskList.ONBOARDING_TASK_LIST_CLOSE : OnboardingTaskList.ONBOARDING_TASK_LIST_OPEN);
|
trackEvent(OnboardingTaskCategory, open ? OnboardingTaskList.ONBOARDING_TASK_LIST_CLOSE : OnboardingTaskList.ONBOARDING_TASK_LIST_OPEN);
|
||||||
|
|
||||||
// check if the AB test FF is set and also check that the linkedBoard has only been shown once, then open the RHS
|
|
||||||
if (autoShowLinkedBoard && open) {
|
|
||||||
const boardsId = findRhsPluginId(suitePluginIds.boards);
|
|
||||||
if (!boardsId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(showRHSPlugin(boardsId));
|
|
||||||
}
|
|
||||||
}, [open, currentUserId]);
|
}, [open, currentUserId]);
|
||||||
|
|
||||||
const openVideoModal = useCallback(() => {
|
const openVideoModal = useCallback(() => {
|
||||||
|
|||||||
@@ -24,9 +24,6 @@ export const OnboardingTaskList = {
|
|||||||
ONBOARDING_TASK_LIST_CLOSE: 'onboarding_task_list_close',
|
ONBOARDING_TASK_LIST_CLOSE: 'onboarding_task_list_close',
|
||||||
ONBOARDING_VIDEO_MODAL: 'onboarding_video_modal',
|
ONBOARDING_VIDEO_MODAL: 'onboarding_video_modal',
|
||||||
DECLINED_ONBOARDING_TASK_LIST: 'declined_onboarding_task_list',
|
DECLINED_ONBOARDING_TASK_LIST: 'declined_onboarding_task_list',
|
||||||
|
|
||||||
// auto show channel linked boards A/B test
|
|
||||||
ONBOARDING_LINKED_BOARD_AUTO_SHOWN: 'linked_board_auto_shown',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GenericTaskSteps = {
|
export const GenericTaskSteps = {
|
||||||
|
|||||||
@@ -2797,8 +2797,6 @@
|
|||||||
"authorize.app": "The app **{appName}** would like the ability to access and modify your basic information.",
|
"authorize.app": "The app **{appName}** would like the ability to access and modify your basic information.",
|
||||||
"authorize.deny": "Deny",
|
"authorize.deny": "Deny",
|
||||||
"authorize.title": "Authorize **{appName}** to Connect to Your **Mattermost** User Account",
|
"authorize.title": "Authorize **{appName}** to Connect to Your **Mattermost** User Account",
|
||||||
"autoShowLinkedBoard.tutorialTip.description": "Manage tasks, plan sprints, conduct standup with the help of kanban boards and tables.",
|
|
||||||
"autoShowLinkedBoard.tutorialTip.title": "Link kanban boards to channels",
|
|
||||||
"avatars.overflowUnnamedOnly": "{overflowUnnamedCount, plural, =1 {one other} other {# others}}",
|
"avatars.overflowUnnamedOnly": "{overflowUnnamedCount, plural, =1 {one other} other {# others}}",
|
||||||
"avatars.overflowUsers": "{overflowUnnamedCount, plural, =0 {{names}} =1 {{names} and one other} other {{names} and # others}}",
|
"avatars.overflowUsers": "{overflowUnnamedCount, plural, =0 {{names}} =1 {{names} and one other} other {{names} and # others}}",
|
||||||
"backstage_list.search": "Search",
|
"backstage_list.search": "Search",
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import {PreferenceType} from '@mattermost/types/preferences';
|
|||||||
|
|
||||||
import {Preferences} from '../constants';
|
import {Preferences} from '../constants';
|
||||||
|
|
||||||
import {OnboardingTaskCategory, OnboardingTaskList} from 'components/onboarding_tasks';
|
|
||||||
|
|
||||||
import {savePreferences} from './preferences';
|
import {savePreferences} from './preferences';
|
||||||
|
|
||||||
export function setNewChannelWithBoardPreference(initializationState: Record<string, boolean>): ActionFunc {
|
export function setNewChannelWithBoardPreference(initializationState: Record<string, boolean>): ActionFunc {
|
||||||
@@ -26,18 +24,3 @@ export function setNewChannelWithBoardPreference(initializationState: Record<str
|
|||||||
return {data: true};
|
return {data: true};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAutoShowLinkedBoardPreference(): ActionFunc {
|
|
||||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
|
||||||
const state = getState();
|
|
||||||
const currentUserId = getCurrentUserId(state);
|
|
||||||
const preference: PreferenceType = {
|
|
||||||
category: OnboardingTaskCategory,
|
|
||||||
user_id: currentUserId,
|
|
||||||
name: OnboardingTaskList.ONBOARDING_LINKED_BOARD_AUTO_SHOWN,
|
|
||||||
value: 'true',
|
|
||||||
};
|
|
||||||
await dispatch(savePreferences(currentUserId, [preference]));
|
|
||||||
return {data: true};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ const Preferences = {
|
|||||||
|
|
||||||
NEW_CHANNEL_WITH_BOARD_TOUR_SHOWED: 'channel_with_board_tip_showed',
|
NEW_CHANNEL_WITH_BOARD_TOUR_SHOWED: 'channel_with_board_tip_showed',
|
||||||
|
|
||||||
AUTO_LINKED_BOARD: 'auto_linked_board',
|
|
||||||
CATEGORY_ONBOARDING: 'category_onboarding',
|
CATEGORY_ONBOARDING: 'category_onboarding',
|
||||||
|
|
||||||
CATEGORY_DRAFTS: 'drafts',
|
CATEGORY_DRAFTS: 'drafts',
|
||||||
|
|||||||
@@ -291,10 +291,6 @@ export function getVisibleDmGmLimit(state: GlobalState) {
|
|||||||
return getInt(state, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.LIMIT_VISIBLE_DMS_GMS, defaultLimit);
|
return getInt(state, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.LIMIT_VISIBLE_DMS_GMS, defaultLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function autoShowLinkedBoardFFEnabled(state: GlobalState): boolean {
|
|
||||||
return getFeatureFlagValue(state, 'OnboardingAutoShowLinkedBoard') === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function onboardingTourTipsEnabled(state: GlobalState): boolean {
|
export function onboardingTourTipsEnabled(state: GlobalState): boolean {
|
||||||
return getFeatureFlagValue(state, 'OnboardingTourTips') === 'true';
|
return getFeatureFlagValue(state, 'OnboardingTourTips') === 'true';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import React, {useCallback} from 'react';
|
|
||||||
import {FormattedMessage} from 'react-intl';
|
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
|
||||||
|
|
||||||
import {Placement} from 'tippy.js';
|
|
||||||
|
|
||||||
import {setAutoShowLinkedBoardPreference} from 'mattermost-redux/actions/boards';
|
|
||||||
|
|
||||||
import {TourTip} from '@mattermost/components';
|
|
||||||
import {shouldShowAutoLinkedBoard} from 'selectors/plugins';
|
|
||||||
import {suitePluginIds} from 'utils/constants';
|
|
||||||
import {getPluggableId} from 'selectors/rhs';
|
|
||||||
import {PluginComponent} from 'types/store/plugins';
|
|
||||||
import {GlobalState} from 'types/store';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
pulsatingDotPlacement?: Omit<Placement, 'auto'| 'auto-end'>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AutoShowLinkedBoardTourTip = ({
|
|
||||||
pulsatingDotPlacement = 'auto',
|
|
||||||
}: Props): JSX.Element | null => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
const rhsPlugins: PluginComponent[] = useSelector((state: GlobalState) => state.plugins.components.RightHandSidebarComponent);
|
|
||||||
const pluggableId = useSelector(getPluggableId);
|
|
||||||
const pluginComponent = rhsPlugins.find((element: PluginComponent) => element.id === pluggableId);
|
|
||||||
|
|
||||||
const isBoards = pluginComponent && (pluginComponent.pluginId === suitePluginIds.focalboard || pluginComponent.pluginId === suitePluginIds.boards);
|
|
||||||
const showAutoLinkedBoard = useSelector(shouldShowAutoLinkedBoard);
|
|
||||||
const showAutoLinkedBoardTourTip = isBoards && showAutoLinkedBoard;
|
|
||||||
|
|
||||||
const title = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='autoShowLinkedBoard.tutorialTip.title'
|
|
||||||
defaultMessage='Link kanban boards to channels'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const screen = (
|
|
||||||
<FormattedMessage
|
|
||||||
id='autoShowLinkedBoard.tutorialTip.description'
|
|
||||||
defaultMessage='Manage tasks, plan sprints, conduct standup with the help of kanban boards and tables.'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleDismiss = useCallback((e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
dispatch(setAutoShowLinkedBoardPreference());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleOpen = useCallback((e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
dispatch(setAutoShowLinkedBoardPreference());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const nextBtn = (
|
|
||||||
<FormattedMessage
|
|
||||||
id={'tutorial_tip.done'}
|
|
||||||
defaultMessage={'Done'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!showAutoLinkedBoardTourTip) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TourTip
|
|
||||||
show={true}
|
|
||||||
screen={screen}
|
|
||||||
title={title}
|
|
||||||
overlayPunchOut={null}
|
|
||||||
placement='left-start'
|
|
||||||
pulsatingDotPlacement={pulsatingDotPlacement}
|
|
||||||
step={1}
|
|
||||||
singleTip={true}
|
|
||||||
showOptOut={false}
|
|
||||||
interactivePunchOut={true}
|
|
||||||
handleDismiss={handleDismiss}
|
|
||||||
handleOpen={handleOpen}
|
|
||||||
handlePrevious={handleDismiss}
|
|
||||||
pulsatingDotTranslate={{x: -10, y: 70}}
|
|
||||||
tippyBlueStyle={true}
|
|
||||||
hideBackdrop={true}
|
|
||||||
nextBtn={nextBtn}
|
|
||||||
handleNext={handleDismiss}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AutoShowLinkedBoardTourTip;
|
|
||||||
@@ -7,8 +7,6 @@ import SearchResultsHeader from 'components/search_results_header';
|
|||||||
|
|
||||||
import Pluggable from 'plugins/pluggable';
|
import Pluggable from 'plugins/pluggable';
|
||||||
|
|
||||||
import AutoShowLinkedBoardTourTip from './auto_show_linked_board_tourtip';
|
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
showPluggable: boolean;
|
showPluggable: boolean;
|
||||||
pluggableId: string;
|
pluggableId: string;
|
||||||
@@ -17,15 +15,12 @@ export type Props = {
|
|||||||
|
|
||||||
export default class RhsPlugin extends React.PureComponent<Props> {
|
export default class RhsPlugin extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const autoLinkedBoardTourTip = (<AutoShowLinkedBoardTourTip/>);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id='rhsContainer'
|
id='rhsContainer'
|
||||||
className='sidebar-right__body'
|
className='sidebar-right__body'
|
||||||
>
|
>
|
||||||
<SearchResultsHeader>
|
<SearchResultsHeader>
|
||||||
{autoLinkedBoardTourTip}
|
|
||||||
{this.props.title}
|
{this.props.title}
|
||||||
</SearchResultsHeader>
|
</SearchResultsHeader>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
|||||||
import {appBarEnabled, getAppBarAppBindings} from 'mattermost-redux/selectors/entities/apps';
|
import {appBarEnabled, getAppBarAppBindings} from 'mattermost-redux/selectors/entities/apps';
|
||||||
import {createShallowSelector} from 'mattermost-redux/utils/helpers';
|
import {createShallowSelector} from 'mattermost-redux/utils/helpers';
|
||||||
|
|
||||||
import {autoShowLinkedBoardFFEnabled, get, getBool} from 'mattermost-redux/selectors/entities/preferences';
|
import {get} from 'mattermost-redux/selectors/entities/preferences';
|
||||||
import {Preferences} from 'mattermost-redux/constants';
|
import {Preferences} from 'mattermost-redux/constants';
|
||||||
|
|
||||||
import {OnboardingTaskCategory, OnboardingTaskList} from 'components/onboarding_tasks';
|
|
||||||
|
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
|
|
||||||
import {AppBinding} from '@mattermost/types/apps';
|
import {AppBinding} from '@mattermost/types/apps';
|
||||||
@@ -104,12 +102,3 @@ export function showNewChannelWithBoardPulsatingDot(state: GlobalState): boolean
|
|||||||
const showPulsatingDot = pulsatingDotState !== '' && JSON.parse(pulsatingDotState)[Preferences.NEW_CHANNEL_WITH_BOARD_TOUR_SHOWED] === false;
|
const showPulsatingDot = pulsatingDotState !== '' && JSON.parse(pulsatingDotState)[Preferences.NEW_CHANNEL_WITH_BOARD_TOUR_SHOWED] === false;
|
||||||
return showPulsatingDot;
|
return showPulsatingDot;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shouldShowAutoLinkedBoard = createSelector(
|
|
||||||
'shouldShowAutoLinkedBoard',
|
|
||||||
(state: GlobalState) => getBool(state, OnboardingTaskCategory, OnboardingTaskList.ONBOARDING_LINKED_BOARD_AUTO_SHOWN),
|
|
||||||
(state: GlobalState) => autoShowLinkedBoardFFEnabled(state),
|
|
||||||
(showAutoLinkedBoardPref: boolean, showAutoLinkedBoardFFEnabled: boolean) => {
|
|
||||||
return !showAutoLinkedBoardPref && showAutoLinkedBoardFFEnabled;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user