Fix maintenance mode banner button color, timezone and date picker UX (#19255)
## Summary - Set `accent="blue"` on InformationBanner action button so it renders blue instead of default gray - Add Banner storybook stories for all color × variant combinations (BluePrimary, BlueSecondary, DangerPrimary, DangerSecondary) - Use `useUserTimezone()` in `SettingsDatePickerInput` instead of browser timezone (`Temporal.Now.timeZoneId()`) so dates respect the admin's profile timezone preference - Separate `onChange` from `onClose` in `SettingsDatePickerInput` so changing the hour no longer forces the date picker to close
This commit is contained in:
+63
-36
@@ -22,7 +22,7 @@ describe('KeyValuePairService', () => {
|
||||
service = new KeyValuePairService(keyValuePairRepository);
|
||||
});
|
||||
|
||||
it('should insert a global null/null key when missing', async () => {
|
||||
it('should upsert a global null/null key', async () => {
|
||||
await service.set({
|
||||
userId: null,
|
||||
workspaceId: null,
|
||||
@@ -31,45 +31,24 @@ describe('KeyValuePairService', () => {
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
});
|
||||
|
||||
expect(keyValuePairRepository.findOne).toHaveBeenCalledWith({
|
||||
where: {
|
||||
userId: expect.any(Object),
|
||||
workspaceId: expect.any(Object),
|
||||
expect(keyValuePairRepository.upsert).toHaveBeenCalledWith(
|
||||
{
|
||||
userId: null,
|
||||
workspaceId: null,
|
||||
key: 'MAINTENANCE_MODE',
|
||||
value: { startAt: '2026-04-02T10:00:00.000Z' },
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
},
|
||||
});
|
||||
expect(keyValuePairRepository.insert).toHaveBeenCalledWith({
|
||||
userId: null,
|
||||
workspaceId: null,
|
||||
key: 'MAINTENANCE_MODE',
|
||||
value: { startAt: '2026-04-02T10:00:00.000Z' },
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
});
|
||||
expect(keyValuePairRepository.upsert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update a global null/null key when present', async () => {
|
||||
keyValuePairRepository.findOne.mockResolvedValue({
|
||||
id: 'existing-id',
|
||||
} as KeyValuePairEntity);
|
||||
|
||||
await service.set({
|
||||
userId: null,
|
||||
workspaceId: null,
|
||||
key: 'MAINTENANCE_MODE',
|
||||
value: { startAt: '2026-04-02T10:00:00.000Z' },
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
});
|
||||
|
||||
expect(keyValuePairRepository.update).toHaveBeenCalledWith('existing-id', {
|
||||
value: { startAt: '2026-04-02T10:00:00.000Z' },
|
||||
});
|
||||
{
|
||||
conflictPaths: ['key'],
|
||||
indexPredicate: '"userId" IS NULL AND "workspaceId" IS NULL',
|
||||
},
|
||||
);
|
||||
expect(keyValuePairRepository.findOne).not.toHaveBeenCalled();
|
||||
expect(keyValuePairRepository.insert).not.toHaveBeenCalled();
|
||||
expect(keyValuePairRepository.upsert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should keep the existing workspace-null index behavior', async () => {
|
||||
it('should upsert with userId-null index when workspaceId is null', async () => {
|
||||
await service.set({
|
||||
userId: 'user-id',
|
||||
workspaceId: null,
|
||||
@@ -87,8 +66,56 @@ describe('KeyValuePairService', () => {
|
||||
type: KeyValuePairType.USER_VARIABLE,
|
||||
},
|
||||
{
|
||||
conflictPaths: ['userId', 'workspaceId', 'key'],
|
||||
indexPredicate: '"workspaceId" is NULL',
|
||||
conflictPaths: ['key', 'userId'],
|
||||
indexPredicate: '"workspaceId" IS NULL',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should upsert with workspaceId-null index when userId is null', async () => {
|
||||
await service.set({
|
||||
userId: null,
|
||||
workspaceId: 'workspace-id',
|
||||
key: 'WORKSPACE_SETTING',
|
||||
value: 'test',
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
});
|
||||
|
||||
expect(keyValuePairRepository.upsert).toHaveBeenCalledWith(
|
||||
{
|
||||
userId: null,
|
||||
workspaceId: 'workspace-id',
|
||||
key: 'WORKSPACE_SETTING',
|
||||
value: 'test',
|
||||
type: KeyValuePairType.CONFIG_VARIABLE,
|
||||
},
|
||||
{
|
||||
conflictPaths: ['key', 'workspaceId'],
|
||||
indexPredicate: '"userId" IS NULL',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should upsert with full conflict paths when both ids are present', async () => {
|
||||
await service.set({
|
||||
userId: 'user-id',
|
||||
workspaceId: 'workspace-id',
|
||||
key: 'USER_WORKSPACE_SETTING',
|
||||
value: 42,
|
||||
type: KeyValuePairType.USER_VARIABLE,
|
||||
});
|
||||
|
||||
expect(keyValuePairRepository.upsert).toHaveBeenCalledWith(
|
||||
{
|
||||
userId: 'user-id',
|
||||
workspaceId: 'workspace-id',
|
||||
key: 'USER_WORKSPACE_SETTING',
|
||||
value: 42,
|
||||
type: KeyValuePairType.USER_VARIABLE,
|
||||
},
|
||||
{
|
||||
conflictPaths: ['key', 'userId', 'workspaceId'],
|
||||
indexPredicate: undefined,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
+12
-34
@@ -82,43 +82,21 @@ export class KeyValuePairService<
|
||||
type,
|
||||
};
|
||||
|
||||
const conflictPaths: string[] = ['key'];
|
||||
let indexPredicate: string | undefined;
|
||||
|
||||
if (hasNullUserAndWorkspace) {
|
||||
const existingKeyValuePair = await keyValuePairRepository.findOne({
|
||||
where: {
|
||||
userId: IsNull(),
|
||||
workspaceId: IsNull(),
|
||||
key,
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingKeyValuePair) {
|
||||
await keyValuePairRepository.update(existingKeyValuePair.id, {
|
||||
value,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await keyValuePairRepository.insert(upsertData);
|
||||
|
||||
return;
|
||||
indexPredicate = '"userId" IS NULL AND "workspaceId" IS NULL';
|
||||
} else if (normalizedUserId === null) {
|
||||
conflictPaths.push('workspaceId');
|
||||
indexPredicate = '"userId" IS NULL';
|
||||
} else if (normalizedWorkspaceId === null) {
|
||||
conflictPaths.push('userId');
|
||||
indexPredicate = '"workspaceId" IS NULL';
|
||||
} else {
|
||||
conflictPaths.push('userId', 'workspaceId');
|
||||
}
|
||||
|
||||
const conflictPaths = Object.keys(upsertData).filter(
|
||||
(conflictPath) =>
|
||||
['userId', 'workspaceId', 'key'].includes(conflictPath) &&
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
upsertData[conflictPath] !== undefined,
|
||||
);
|
||||
|
||||
const indexPredicate =
|
||||
normalizedUserId === null
|
||||
? '"userId" is NULL'
|
||||
: normalizedWorkspaceId === null
|
||||
? '"workspaceId" is NULL'
|
||||
: undefined;
|
||||
|
||||
await keyValuePairRepository.upsert(upsertData, {
|
||||
conflictPaths,
|
||||
indexPredicate,
|
||||
|
||||
Reference in New Issue
Block a user