Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@
border-radius: 4px;
background: rgba(var(--center-channel-color-rgb), 0.08);
color: rgba(var(--center-channel-color-rgb), 0.75);
cursor: pointer;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
user-select: none;
}

.SessionAttributes__type-cell {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function makeField(name: string, type: 'text' | 'select', sortOrder: number, ext
}

const representativeFields: UserPropertyField[] = [
makeField('ip_address', 'text', 0, {
makeField('client_ip_address', 'text', 0, {
display_name: 'Client IP',
platforms: ['desktop', 'browser'],
ttl_seconds: 300,
Expand Down Expand Up @@ -212,9 +212,9 @@ describe('SessionAttributesPage', () => {

await screen.findByText('Client IP');

await userEvent.click(screen.getByTestId('session-attribute-dotmenu-session-ip_address'));
await userEvent.click(screen.getByTestId('session-attribute-dotmenu-session-client_ip_address'));
await userEvent.hover(screen.getByRole('menuitem', {name: /Time-to-live/}));
await userEvent.click(await screen.findByTestId('session-attribute-ttl-option-session-ip_address-3600'));
await userEvent.click(await screen.findByTestId('session-attribute-ttl-option-session-client_ip_address-3600'));

const saveButton = screen.getByRole('button', {name: /Save/});
expect(saveButton).toBeEnabled();
Expand All @@ -228,7 +228,7 @@ describe('SessionAttributesPage', () => {
expect(patchPropertyField).toHaveBeenCalledWith(
SESSION_ATTRIBUTES_GROUP_ID,
SESSION_ATTRIBUTES_OBJECT_TYPE,
'session-ip_address',
'session-client_ip_address',
{attrs: {ttl_seconds: 3600}},
);
});
Expand All @@ -255,9 +255,9 @@ describe('SessionAttributesPage', () => {
await screen.findByText('Client IP');
expect(store.getState().views.admin.navigationBlock.blocked).toBe(false);

await userEvent.click(screen.getByTestId('session-attribute-dotmenu-session-ip_address'));
await userEvent.click(screen.getByTestId('session-attribute-dotmenu-session-client_ip_address'));
await userEvent.hover(screen.getByRole('menuitem', {name: /Time-to-live/}));
await userEvent.click(await screen.findByTestId('session-attribute-ttl-option-session-ip_address-3600'));
await userEvent.click(await screen.findByTestId('session-attribute-ttl-option-session-client_ip_address-3600'));

await waitFor(() => {
expect(store.getState().views.admin.navigationBlock.blocked).toBe(true);
Expand All @@ -277,9 +277,9 @@ describe('SessionAttributesPage', () => {

await screen.findByText('Client IP');

await userEvent.click(screen.getByTestId('session-attribute-dotmenu-session-ip_address'));
await userEvent.click(screen.getByTestId('session-attribute-dotmenu-session-client_ip_address'));
await userEvent.hover(screen.getByRole('menuitem', {name: /Time-to-live/}));
await userEvent.click(await screen.findByTestId('session-attribute-ttl-option-session-ip_address-3600'));
await userEvent.click(await screen.findByTestId('session-attribute-ttl-option-session-client_ip_address-3600'));

const stagedRow = screen.getAllByText('Client IP')[0].closest('tr') as HTMLElement;
expect(within(stagedRow).getByTestId('session-attribute-ttl')).toHaveTextContent('1h');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function makeField(name: string, extra: ExtraAttrs = {}): SessionAttributeField
} as UserPropertyField;
}

const enabledField = makeField('ip_address', {
const enabledField = makeField('client_ip_address', {
display_name: 'Client IP',
enabled: true,
ttl_seconds: 300,
Expand All @@ -56,6 +56,13 @@ const disabledField = makeField('vpn_active', {
grace_period_seconds: 30,
});

const serverSourcedField = makeField('ip_address', {
display_name: 'IP Address',
enabled: true,
ttl_seconds: 300,
grace_period_seconds: 60,
});

function renderMenu(field: SessionAttributeField, onStageChange = jest.fn()) {
renderWithContext(
<div>
Expand Down Expand Up @@ -125,6 +132,15 @@ describe('SessionAttributesDotMenu', () => {
expect(unselected).toHaveAttribute('aria-checked', 'false');
});

it('hides the TTL and Grace submenus for server-sourced attributes', async () => {
renderMenu(serverSourcedField);

await userEvent.click(screen.getByTestId(`session-attribute-dotmenu-${serverSourcedField.id}`));

expect(screen.queryByRole('menuitem', {name: /Time-to-live/})).not.toBeInTheDocument();
expect(screen.queryByRole('menuitem', {name: /Grace Period/})).not.toBeInTheDocument();
});

it('opens the confirmation modal for Disable without staging immediately', async () => {
const onStageChange = renderMenu(enabledField);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {ModalIdentifiers} from 'utils/constants';

import DisableAttributeModal from './disable_attribute_modal';
import type {StagedAttrs} from './use_session_attribute_edits';
import {DURATION_PRESETS_SECONDS, formatDuration, getSessionAttrs, getSessionDisplayName} from './utils';
import {DURATION_PRESETS_SECONDS, formatDuration, getSessionAttrs, getSessionDisplayName, isServerSourced} from './utils';
import type {SessionAttributeField} from './utils';

type Props = {
Expand All @@ -30,6 +30,7 @@ export default function SessionAttributesDotMenu({field, onStageChange, disabled

const attrs = getSessionAttrs(field);
const menuId = `session-attribute-dotmenu-${field.id}`;
const isServerAttribute = isServerSourced(field.name);

const handleTtlChange = useCallback((seconds: number) => {
onStageChange(field.id, {ttl_seconds: seconds});
Expand Down Expand Up @@ -80,45 +81,49 @@ export default function SessionAttributesDotMenu({field, onStageChange, disabled
'aria-label': formatMessage({id: 'admin.session_attributes.dotmenu.menu.aria_label', defaultMessage: 'Select an action'}),
}}
>
<Menu.SubMenu
id={`${menuId}-ttl`}
menuId={`${menuId}-ttl-menu`}
leadingElement={<UpdateIcon size={18}/>}
labels={(
<FormattedMessage
id='admin.session_attributes.dotmenu.ttl.label'
defaultMessage='Time-to-live (TTL)'
/>
)}
trailingElements={(
<>
{formatDuration(attrs.ttl_seconds)}
<ChevronRightIcon size={16}/>
</>
)}
>
{renderPresets(attrs.ttl_seconds, `session-attribute-ttl-option-${field.id}`, handleTtlChange)}
</Menu.SubMenu>
<Menu.SubMenu
id={`${menuId}-grace`}
menuId={`${menuId}-grace-menu`}
leadingElement={<AlertCircleOutlineIcon size={18}/>}
labels={(
<FormattedMessage
id='admin.session_attributes.dotmenu.grace.label'
defaultMessage='Grace Period'
/>
)}
trailingElements={(
<>
{formatDuration(attrs.grace_period_seconds)}
<ChevronRightIcon size={16}/>
</>
)}
>
{renderPresets(attrs.grace_period_seconds, `session-attribute-grace-option-${field.id}`, (seconds) => onStageChange(field.id, {grace_period_seconds: seconds}))}
</Menu.SubMenu>
<Menu.Separator/>
{!isServerAttribute && ([
<Menu.SubMenu
key={`${menuId}-ttl`}
id={`${menuId}-ttl`}
menuId={`${menuId}-ttl-menu`}
leadingElement={<UpdateIcon size={18}/>}
labels={(
<FormattedMessage
id='admin.session_attributes.dotmenu.ttl.label'
defaultMessage='Time-to-live (TTL)'
/>
)}
trailingElements={(
<>
{formatDuration(attrs.ttl_seconds)}
<ChevronRightIcon size={16}/>
</>
)}
>
{renderPresets(attrs.ttl_seconds, `session-attribute-ttl-option-${field.id}`, handleTtlChange)}
</Menu.SubMenu>,
<Menu.SubMenu
key={`${menuId}-grace`}
id={`${menuId}-grace`}
menuId={`${menuId}-grace-menu`}
leadingElement={<AlertCircleOutlineIcon size={18}/>}
labels={(
<FormattedMessage
id='admin.session_attributes.dotmenu.grace.label'
defaultMessage='Grace Period'
/>
)}
trailingElements={(
<>
{formatDuration(attrs.grace_period_seconds)}
<ChevronRightIcon size={16}/>
</>
)}
>
{renderPresets(attrs.grace_period_seconds, `session-attribute-grace-option-${field.id}`, (seconds) => onStageChange(field.id, {grace_period_seconds: seconds}))}
</Menu.SubMenu>,
<Menu.Separator key={`${menuId}-separator`}/>,
])}
{attrs.enabled ? (
<Menu.Item
id={`session-attribute-disable-${field.id}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const fields: SessionAttributeField[] = [
makeField('client_ip_address', 'text', {
sort_order: 1,
platforms: ['desktop', 'mobile', 'browser'],
ttl_seconds: 300,
grace_period_seconds: 60,
enabled: false,
}),
makeField('os_version', 'text', {sort_order: 2, enabled: true}),
Expand Down Expand Up @@ -189,11 +191,24 @@ describe('SessionAttributesTable', () => {
/>,
);

const row = rowFor('Client IP');
const row = rowFor('client_ip_address');
expect(within(row).getByTestId('session-attribute-ttl')).toHaveTextContent('5m');
expect(within(row).getByTestId('session-attribute-grace')).toHaveTextContent('1m');
});

it('shows a dash for TTL and Grace on server-sourced attributes', () => {
renderWithContext(
<SessionAttributesTable
data={fields}
onStageChange={onStageChange}
/>,
);

const row = rowFor('Client IP');
expect(within(row).getByTestId('session-attribute-ttl')).toHaveTextContent('—');
expect(within(row).getByTestId('session-attribute-grace')).toHaveTextContent('—');
});

it('falls back to the field name when display_name is absent', () => {
renderWithContext(
<SessionAttributesTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {FormattedMessage, defineMessages} from 'react-intl';

import {CheckboxMarkedCircleOutlineIcon, ChevronDownCircleOutlineIcon, MenuVariantIcon} from '@mattermost/compass-icons/components';
import type IconProps from '@mattermost/compass-icons/components/props';
import {WithTooltip} from '@mattermost/shared/components/tooltip';

import PlatformIcons from './platform_icons';
import SessionAttributesDotMenu from './session_attributes_dot_menu';
Expand Down Expand Up @@ -78,15 +79,19 @@ export default function SessionAttributesTable({data, onStageChange, disabled =
<span className='SessionAttributes__name-cell'>
<span className='SessionAttributes__name-text'>{getValue()}</span>
{isServerSourced(row.original.name) && (
<span
className='SessionAttributes__server-badge'
data-testid='session-attribute-server-label'
<WithTooltip
title={messages.serverLabelTooltip}
>
<FormattedMessage
id='admin.session_attributes.table.server_label'
defaultMessage='Server'
/>
</span>
<span
className='SessionAttributes__server-badge'
data-testid='session-attribute-server-label'
>
<FormattedMessage
id='admin.session_attributes.table.server_label'
defaultMessage='Server'
/>
</span>
</WithTooltip>
)}
</span>
),
Expand Down Expand Up @@ -154,7 +159,7 @@ export default function SessionAttributesTable({data, onStageChange, disabled =
className='SessionAttributes__duration'
data-testid='session-attribute-ttl'
>
{formatDuration(getSessionAttrs(row.original).ttl_seconds)}
{isServerSourced(row.original.name) ? '—' : formatDuration(getSessionAttrs(row.original).ttl_seconds)}
</span>
),
enableHiding: false,
Expand All @@ -176,7 +181,7 @@ export default function SessionAttributesTable({data, onStageChange, disabled =
className='SessionAttributes__duration'
data-testid='session-attribute-grace'
>
{formatDuration(getSessionAttrs(row.original).grace_period_seconds)}
{isServerSourced(row.original.name) ? '—' : formatDuration(getSessionAttrs(row.original).grace_period_seconds)}
</span>
),
enableHiding: false,
Expand Down Expand Up @@ -257,3 +262,10 @@ const typeLabels = defineMessages({
Boolean: {id: 'admin.session_attributes.type.boolean', defaultMessage: 'Boolean'},
Enum: {id: 'admin.session_attributes.type.enum', defaultMessage: 'Enum'},
});

const messages = defineMessages({
serverLabelTooltip: {
id: 'admin.session_attributes.table.server_label.tooltip',
defaultMessage: 'This attribute is captured by the server for every request a user makes. It is not delivered by the client.',
},
});
1 change: 1 addition & 0 deletions webapp/channels/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3337,6 +3337,7 @@
"admin.session_attributes.table.name": "Name",
"admin.session_attributes.table.platform": "Platform",
"admin.session_attributes.table.server_label": "Server",
"admin.session_attributes.table.server_label.tooltip": "This attribute is captured by the server for every request a user makes. It is not delivered by the client.",
"admin.session_attributes.table.status": "Status",
"admin.session_attributes.table.ttl": "TTL",
"admin.session_attributes.table.type": "Type",
Expand Down
Loading