mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
Remember current standard editor tab (#53644)
* Remember current standard editor tab * review
This commit is contained in:
@@ -112,13 +112,10 @@ test('rendering and switching tabs for new role', async () => {
|
|||||||
expect(screen.getByRole('button', { name: 'Create Role' })).toBeEnabled();
|
expect(screen.getByRole('button', { name: 'Create Role' })).toBeEnabled();
|
||||||
|
|
||||||
await user.click(getStandardEditorTab());
|
await user.click(getStandardEditorTab());
|
||||||
await screen.findByLabelText('Role Name *');
|
await screen.findByLabelText('Max Session TTL');
|
||||||
expect(
|
expect(
|
||||||
screen.queryByRole('button', { name: /Reset to Standard Settings/i })
|
screen.queryByRole('button', { name: /Reset to Standard Settings/i })
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
await forwardToTab('Resources');
|
|
||||||
await forwardToTab('Admin Rules');
|
|
||||||
await forwardToTab('Options');
|
|
||||||
expect(screen.getByRole('button', { name: 'Create Role' })).toBeEnabled();
|
expect(screen.getByRole('button', { name: 'Create Role' })).toBeEnabled();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { produce } from 'immer';
|
import { useCallback, useId } from 'react';
|
||||||
import { useCallback, useId, useState } from 'react';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { Box, ButtonPrimary, ButtonSecondary, Flex } from 'design';
|
import { Box, ButtonPrimary, ButtonSecondary, Flex } from 'design';
|
||||||
@@ -37,6 +36,7 @@ import {
|
|||||||
OptionsModel,
|
OptionsModel,
|
||||||
roleEditorModelToRole,
|
roleEditorModelToRole,
|
||||||
StandardEditorModel,
|
StandardEditorModel,
|
||||||
|
StandardEditorTab,
|
||||||
} from './standardmodel';
|
} from './standardmodel';
|
||||||
import { ActionType, StandardModelDispatcher } from './useStandardModel';
|
import { ActionType, StandardModelDispatcher } from './useStandardModel';
|
||||||
|
|
||||||
@@ -60,20 +60,10 @@ export const StandardEditor = ({
|
|||||||
dispatch,
|
dispatch,
|
||||||
}: StandardEditorProps) => {
|
}: StandardEditorProps) => {
|
||||||
const isEditing = !!originalRole;
|
const isEditing = !!originalRole;
|
||||||
const { roleModel, validationResult } = standardEditorModel;
|
const { roleModel, validationResult, currentTab, disabledTabs } =
|
||||||
|
standardEditorModel;
|
||||||
const modelValid = validationResult.isValid;
|
const modelValid = validationResult.isValid;
|
||||||
|
|
||||||
enum StandardEditorTab {
|
|
||||||
Overview,
|
|
||||||
Resources,
|
|
||||||
AdminRules,
|
|
||||||
Options,
|
|
||||||
}
|
|
||||||
|
|
||||||
const [currentTab, setCurrentTab] = useState(StandardEditorTab.Overview);
|
|
||||||
const [disabledTabs, setDisabledTabs] = useState(
|
|
||||||
isEditing ? [false, false, false, false] : [false, true, true, true]
|
|
||||||
);
|
|
||||||
const idPrefix = useId();
|
const idPrefix = useId();
|
||||||
|
|
||||||
const validator = useValidation();
|
const validator = useValidation();
|
||||||
@@ -104,6 +94,13 @@ export const StandardEditor = ({
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setCurrentTab = useCallback(
|
||||||
|
(newTab: StandardEditorTab) => {
|
||||||
|
dispatch({ type: ActionType.SetCurrentTab, payload: newTab });
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const validateAndGoToNextTab = useCallback(() => {
|
const validateAndGoToNextTab = useCallback(() => {
|
||||||
const nextTabIndex = currentTab + 1;
|
const nextTabIndex = currentTab + 1;
|
||||||
const valid = validate();
|
const valid = validate();
|
||||||
@@ -112,12 +109,7 @@ export const StandardEditor = ({
|
|||||||
}
|
}
|
||||||
validator.reset();
|
validator.reset();
|
||||||
setCurrentTab(nextTabIndex);
|
setCurrentTab(nextTabIndex);
|
||||||
setDisabledTabs(prevEnabledTabs =>
|
}, [currentTab, validate, validator, setCurrentTab]);
|
||||||
produce(prevEnabledTabs, et => {
|
|
||||||
et[nextTabIndex] = false;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [currentTab, validate, validator, setCurrentTab, setDisabledTabs]);
|
|
||||||
|
|
||||||
const goToPreviousTab = useCallback(
|
const goToPreviousTab = useCallback(
|
||||||
() => setCurrentTab(currentTab - 1),
|
() => setCurrentTab(currentTab - 1),
|
||||||
@@ -136,7 +128,7 @@ export const StandardEditor = ({
|
|||||||
return {
|
return {
|
||||||
key: tab,
|
key: tab,
|
||||||
title: tabTitles[tab],
|
title: tabTitles[tab],
|
||||||
disabled: disabledTabs[tab],
|
disabled: disabledTabs.has(tab),
|
||||||
controls: tabElementIDs[tab],
|
controls: tabElementIDs[tab],
|
||||||
status: error ? validationErrorTabStatus : undefined,
|
status: error ? validationErrorTabStatus : undefined,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ import {
|
|||||||
import { RoleEditorModelValidationResult } from './validation';
|
import { RoleEditorModelValidationResult } from './validation';
|
||||||
import { defaultOptions } from './withDefaults';
|
import { defaultOptions } from './withDefaults';
|
||||||
|
|
||||||
|
export enum StandardEditorTab {
|
||||||
|
Overview,
|
||||||
|
Resources,
|
||||||
|
AdminRules,
|
||||||
|
Options,
|
||||||
|
}
|
||||||
|
|
||||||
export type StandardEditorModel = {
|
export type StandardEditorModel = {
|
||||||
/**
|
/**
|
||||||
* The role model. Can be undefined if there was an unhandled error when
|
* The role model. Can be undefined if there was an unhandled error when
|
||||||
@@ -70,6 +77,8 @@ export type StandardEditorModel = {
|
|||||||
* role.
|
* role.
|
||||||
*/
|
*/
|
||||||
validationResult?: RoleEditorModelValidationResult;
|
validationResult?: RoleEditorModelValidationResult;
|
||||||
|
currentTab: StandardEditorTab;
|
||||||
|
disabledTabs: Set<StandardEditorTab>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { current, original } from 'immer';
|
import { current, enableMapSet, original } from 'immer';
|
||||||
import { Dispatch } from 'react';
|
import { Dispatch } from 'react';
|
||||||
import { useImmerReducer } from 'use-immer';
|
import { useImmerReducer } from 'use-immer';
|
||||||
|
|
||||||
@@ -38,11 +38,15 @@ import {
|
|||||||
RoleEditorModel,
|
RoleEditorModel,
|
||||||
roleToRoleEditorModel,
|
roleToRoleEditorModel,
|
||||||
StandardEditorModel,
|
StandardEditorModel,
|
||||||
|
StandardEditorTab,
|
||||||
} from './standardmodel';
|
} from './standardmodel';
|
||||||
import { validateRoleEditorModel } from './validation';
|
import { validateRoleEditorModel } from './validation';
|
||||||
|
|
||||||
const logger = new Logger('useStandardModel');
|
const logger = new Logger('useStandardModel');
|
||||||
|
|
||||||
|
// Enable support for the Set type in Immer. We use it for `disabledTabs`.
|
||||||
|
enableMapSet();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a standard model state and returns an array composed of the state
|
* Creates a standard model state and returns an array composed of the state
|
||||||
* and an action dispatcher that can be used to change it. Since the conversion
|
* and an action dispatcher that can be used to change it. Since the conversion
|
||||||
@@ -56,6 +60,7 @@ export const useStandardModel = (
|
|||||||
useImmerReducer(reduce, originalRole, initializeState);
|
useImmerReducer(reduce, originalRole, initializeState);
|
||||||
|
|
||||||
const initializeState = (originalRole?: Role): StandardEditorModel => {
|
const initializeState = (originalRole?: Role): StandardEditorModel => {
|
||||||
|
const isEditing = !!originalRole;
|
||||||
const role = originalRole ?? newRole();
|
const role = originalRole ?? newRole();
|
||||||
const roleModel = safelyConvertRoleToEditorModel(role);
|
const roleModel = safelyConvertRoleToEditorModel(role);
|
||||||
return {
|
return {
|
||||||
@@ -64,6 +69,14 @@ const initializeState = (originalRole?: Role): StandardEditorModel => {
|
|||||||
isDirty: !originalRole, // New role is dirty by default.
|
isDirty: !originalRole, // New role is dirty by default.
|
||||||
validationResult:
|
validationResult:
|
||||||
roleModel && validateRoleEditorModel(roleModel, undefined, undefined),
|
roleModel && validateRoleEditorModel(roleModel, undefined, undefined),
|
||||||
|
currentTab: StandardEditorTab.Overview,
|
||||||
|
disabledTabs: isEditing
|
||||||
|
? new Set()
|
||||||
|
: new Set([
|
||||||
|
StandardEditorTab.Resources,
|
||||||
|
StandardEditorTab.AdminRules,
|
||||||
|
StandardEditorTab.Options,
|
||||||
|
]),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -82,6 +95,7 @@ const safelyConvertRoleToEditorModel = (
|
|||||||
export type StandardModelDispatcher = Dispatch<StandardModelAction>;
|
export type StandardModelDispatcher = Dispatch<StandardModelAction>;
|
||||||
|
|
||||||
export enum ActionType {
|
export enum ActionType {
|
||||||
|
SetCurrentTab = 'SetCurrentTab',
|
||||||
SetModel = 'SetModel',
|
SetModel = 'SetModel',
|
||||||
ResetToStandard = 'ResetToStandard',
|
ResetToStandard = 'ResetToStandard',
|
||||||
SetMetadata = 'SetMetadata',
|
SetMetadata = 'SetMetadata',
|
||||||
@@ -99,6 +113,7 @@ export enum ActionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StandardModelAction =
|
type StandardModelAction =
|
||||||
|
| SetCurrentTabAction
|
||||||
| SetModelAction
|
| SetModelAction
|
||||||
| ResetToStandardAction
|
| ResetToStandardAction
|
||||||
| SetMetadataAction
|
| SetMetadataAction
|
||||||
@@ -115,6 +130,10 @@ type StandardModelAction =
|
|||||||
| EnableValidationAction;
|
| EnableValidationAction;
|
||||||
|
|
||||||
/** Sets the entire model. */
|
/** Sets the entire model. */
|
||||||
|
type SetCurrentTabAction = {
|
||||||
|
type: ActionType.SetCurrentTab;
|
||||||
|
payload: StandardEditorTab;
|
||||||
|
};
|
||||||
type SetModelAction = { type: ActionType.SetModel; payload: RoleEditorModel };
|
type SetModelAction = { type: ActionType.SetModel; payload: RoleEditorModel };
|
||||||
type ResetToStandardAction = {
|
type ResetToStandardAction = {
|
||||||
type: ActionType.ResetToStandard;
|
type: ActionType.ResetToStandard;
|
||||||
@@ -175,6 +194,11 @@ const reduce = (
|
|||||||
// This reduce uses Immer, so we modify the model draft directly.
|
// This reduce uses Immer, so we modify the model draft directly.
|
||||||
// TODO(bl-nero): add immutability to the model data types.
|
// TODO(bl-nero): add immutability to the model data types.
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case ActionType.SetCurrentTab:
|
||||||
|
state.currentTab = payload;
|
||||||
|
state.disabledTabs.delete(payload);
|
||||||
|
break;
|
||||||
|
|
||||||
case ActionType.SetModel:
|
case ActionType.SetModel:
|
||||||
state.roleModel = payload;
|
state.roleModel = payload;
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user