datajunction-ui 0.0.27-alpha.0 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/app/components/NamespaceHeader.jsx +96 -26
- package/src/app/components/__tests__/NamespaceHeader.test.jsx +165 -0
- package/src/app/pages/NamespacePage/Explorer.jsx +68 -10
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +21 -11
- package/src/app/pages/NamespacePage/index.jsx +316 -47
- package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +28 -0
- package/src/app/pages/QueryPlannerPage/PreAggDetailsPanel.jsx +20 -20
- package/src/app/pages/QueryPlannerPage/index.jsx +1 -1
- package/src/app/pages/Root/__tests__/index.test.jsx +99 -4
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +177 -0
- package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +50 -0
- package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +95 -0
- package/src/app/pages/SettingsPage/__tests__/index.test.jsx +315 -28
- package/src/app/services/DJService.js +33 -0
- package/src/app/utils/__tests__/date.test.js +60 -140
- package/src/styles/index.css +51 -10
|
@@ -2,197 +2,117 @@ import { formatRelativeTime, getDateGroup, groupByDate } from '../date';
|
|
|
2
2
|
|
|
3
3
|
describe('date utilities', () => {
|
|
4
4
|
describe('formatRelativeTime', () => {
|
|
5
|
-
it('returns "just now" for times less than
|
|
5
|
+
it('returns "just now" for times less than a minute ago', () => {
|
|
6
6
|
const now = new Date();
|
|
7
7
|
expect(formatRelativeTime(now.toISOString())).toBe('just now');
|
|
8
|
-
|
|
9
|
-
const thirtySecondsAgo = new Date(Date.now() - 30000);
|
|
10
|
-
expect(formatRelativeTime(thirtySecondsAgo.toISOString())).toBe(
|
|
11
|
-
'just now',
|
|
12
|
-
);
|
|
13
8
|
});
|
|
14
9
|
|
|
15
|
-
it('returns minutes
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const thirtyMinutesAgo = new Date(Date.now() - 30 * 60000);
|
|
20
|
-
expect(formatRelativeTime(thirtyMinutesAgo.toISOString())).toBe(
|
|
21
|
-
'30m ago',
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
const fiftyNineMinutesAgo = new Date(Date.now() - 59 * 60000);
|
|
25
|
-
expect(formatRelativeTime(fiftyNineMinutesAgo.toISOString())).toBe(
|
|
26
|
-
'59m ago',
|
|
27
|
-
);
|
|
10
|
+
it('returns minutes for times less than an hour ago', () => {
|
|
11
|
+
const date = new Date();
|
|
12
|
+
date.setMinutes(date.getMinutes() - 30);
|
|
13
|
+
expect(formatRelativeTime(date.toISOString())).toBe('30m ago');
|
|
28
14
|
});
|
|
29
15
|
|
|
30
|
-
it('returns hours
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const twelveHoursAgo = new Date(Date.now() - 12 * 3600000);
|
|
35
|
-
expect(formatRelativeTime(twelveHoursAgo.toISOString())).toBe('12h ago');
|
|
36
|
-
|
|
37
|
-
const twentyThreeHoursAgo = new Date(Date.now() - 23 * 3600000);
|
|
38
|
-
expect(formatRelativeTime(twentyThreeHoursAgo.toISOString())).toBe(
|
|
39
|
-
'23h ago',
|
|
40
|
-
);
|
|
16
|
+
it('returns hours for times less than a day ago', () => {
|
|
17
|
+
const date = new Date();
|
|
18
|
+
date.setHours(date.getHours() - 5);
|
|
19
|
+
expect(formatRelativeTime(date.toISOString())).toBe('5h ago');
|
|
41
20
|
});
|
|
42
21
|
|
|
43
|
-
it('returns days
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const threeDaysAgo = new Date(Date.now() - 3 * 86400000);
|
|
48
|
-
expect(formatRelativeTime(threeDaysAgo.toISOString())).toBe('3d ago');
|
|
49
|
-
|
|
50
|
-
const sixDaysAgo = new Date(Date.now() - 6 * 86400000);
|
|
51
|
-
expect(formatRelativeTime(sixDaysAgo.toISOString())).toBe('6d ago');
|
|
22
|
+
it('returns days for times less than a week ago', () => {
|
|
23
|
+
const date = new Date();
|
|
24
|
+
date.setDate(date.getDate() - 3);
|
|
25
|
+
expect(formatRelativeTime(date.toISOString())).toBe('3d ago');
|
|
52
26
|
});
|
|
53
27
|
|
|
54
|
-
it('returns formatted date for times
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expect(result).
|
|
59
|
-
expect(result).toMatch(/\d/); // Should contain numbers (date)
|
|
28
|
+
it('returns formatted date for times more than a week ago', () => {
|
|
29
|
+
const date = new Date();
|
|
30
|
+
date.setDate(date.getDate() - 10);
|
|
31
|
+
const result = formatRelativeTime(date.toISOString());
|
|
32
|
+
expect(result).toMatch(/\d{1,2}\/\d{1,2}\/\d{4}/);
|
|
60
33
|
});
|
|
61
34
|
});
|
|
62
35
|
|
|
63
36
|
describe('getDateGroup', () => {
|
|
64
|
-
it('returns "Today" for
|
|
37
|
+
it('returns "Today" for today\'s date', () => {
|
|
65
38
|
const now = new Date();
|
|
66
39
|
expect(getDateGroup(now.toISOString())).toBe('Today');
|
|
67
|
-
|
|
68
|
-
// Earlier today (midnight)
|
|
69
|
-
const midnight = new Date();
|
|
70
|
-
midnight.setHours(0, 0, 0, 0);
|
|
71
|
-
expect(getDateGroup(midnight.toISOString())).toBe('Today');
|
|
72
40
|
});
|
|
73
41
|
|
|
74
|
-
it('returns "Yesterday" for
|
|
42
|
+
it('returns "Yesterday" for yesterday\'s date', () => {
|
|
75
43
|
const yesterday = new Date();
|
|
76
44
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
77
45
|
expect(getDateGroup(yesterday.toISOString())).toBe('Yesterday');
|
|
78
46
|
});
|
|
79
47
|
|
|
80
|
-
it('returns "
|
|
48
|
+
it('returns "Last Week" for dates from last week', () => {
|
|
81
49
|
const now = new Date();
|
|
82
50
|
const dayOfWeek = now.getDay();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (dayOfWeek >= 2) {
|
|
87
|
-
const twoDaysAgo = new Date();
|
|
88
|
-
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
|
|
89
|
-
expect(getDateGroup(twoDaysAgo.toISOString())).toBe('This Week');
|
|
90
|
-
}
|
|
51
|
+
const lastWeek = new Date();
|
|
52
|
+
lastWeek.setDate(lastWeek.getDate() - dayOfWeek - 3); // Go to last week
|
|
53
|
+
expect(getDateGroup(lastWeek.toISOString())).toBe('Last Week');
|
|
91
54
|
});
|
|
92
55
|
|
|
93
|
-
it('returns "
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const thisWeekStart = new Date(today);
|
|
100
|
-
thisWeekStart.setDate(thisWeekStart.getDate() - dayOfWeek);
|
|
56
|
+
it('returns "Older" for dates more than two weeks ago', () => {
|
|
57
|
+
const oldDate = new Date();
|
|
58
|
+
oldDate.setDate(oldDate.getDate() - 20);
|
|
59
|
+
expect(getDateGroup(oldDate.toISOString())).toBe('Older');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
101
62
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
63
|
+
describe('getDateGroup with mocked time', () => {
|
|
64
|
+
// Test "This Week" branch with a fixed date (Wednesday, Jan 15, 2025)
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
jest.useFakeTimers();
|
|
67
|
+
jest.setSystemTime(new Date('2025-01-15T12:00:00Z'));
|
|
68
|
+
});
|
|
105
69
|
|
|
106
|
-
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
jest.useRealTimers();
|
|
107
72
|
});
|
|
108
73
|
|
|
109
|
-
it('returns "
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
74
|
+
it('returns "This Week" for dates earlier this week', () => {
|
|
75
|
+
// Jan 15, 2025 is Wednesday. Week starts Sunday Jan 12.
|
|
76
|
+
// A date from Monday Jan 13 should be "This Week"
|
|
77
|
+
const thisWeekDate = new Date('2025-01-13T12:00:00Z');
|
|
78
|
+
expect(getDateGroup(thisWeekDate.toISOString())).toBe('This Week');
|
|
79
|
+
});
|
|
113
80
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
81
|
+
it('returns "This Week" for Sunday of the current week', () => {
|
|
82
|
+
// Sunday Jan 12, 2025 is the start of this week
|
|
83
|
+
const sundayThisWeek = new Date('2025-01-12T12:00:00Z');
|
|
84
|
+
expect(getDateGroup(sundayThisWeek.toISOString())).toBe('This Week');
|
|
117
85
|
});
|
|
118
86
|
});
|
|
119
87
|
|
|
120
88
|
describe('groupByDate', () => {
|
|
121
|
-
it('groups items by
|
|
89
|
+
it('groups items by date', () => {
|
|
122
90
|
const now = new Date();
|
|
123
91
|
const yesterday = new Date();
|
|
124
92
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
125
|
-
const lastMonth = new Date();
|
|
126
|
-
lastMonth.setMonth(lastMonth.getMonth() - 1);
|
|
127
93
|
|
|
128
94
|
const items = [
|
|
129
95
|
{ id: 1, created_at: now.toISOString() },
|
|
130
|
-
{ id: 2, created_at:
|
|
131
|
-
{ id: 3, created_at: yesterday.toISOString() },
|
|
132
|
-
{ id: 4, created_at: lastMonth.toISOString() },
|
|
96
|
+
{ id: 2, created_at: yesterday.toISOString() },
|
|
133
97
|
];
|
|
134
98
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
expect(
|
|
138
|
-
|
|
139
|
-
const todayGroup = result.find(g => g.label === 'Today');
|
|
140
|
-
expect(todayGroup.items).toHaveLength(2);
|
|
141
|
-
expect(todayGroup.items.map(i => i.id)).toEqual([1, 2]);
|
|
142
|
-
|
|
143
|
-
const yesterdayGroup = result.find(g => g.label === 'Yesterday');
|
|
144
|
-
expect(yesterdayGroup.items).toHaveLength(1);
|
|
145
|
-
expect(yesterdayGroup.items[0].id).toBe(3);
|
|
146
|
-
|
|
147
|
-
const olderGroup = result.find(g => g.label === 'Older');
|
|
148
|
-
expect(olderGroup.items).toHaveLength(1);
|
|
149
|
-
expect(olderGroup.items[0].id).toBe(4);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('returns groups in correct order', () => {
|
|
153
|
-
const now = new Date();
|
|
154
|
-
const yesterday = new Date();
|
|
155
|
-
yesterday.setDate(yesterday.getDate() - 1);
|
|
156
|
-
const lastMonth = new Date();
|
|
157
|
-
lastMonth.setMonth(lastMonth.getMonth() - 1);
|
|
158
|
-
|
|
159
|
-
// Items in random order
|
|
160
|
-
const items = [
|
|
161
|
-
{ id: 1, created_at: lastMonth.toISOString() },
|
|
162
|
-
{ id: 2, created_at: now.toISOString() },
|
|
163
|
-
{ id: 3, created_at: yesterday.toISOString() },
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
const result = groupByDate(items);
|
|
167
|
-
const labels = result.map(g => g.label);
|
|
168
|
-
|
|
169
|
-
// Should be in order: Today, Yesterday, Older
|
|
170
|
-
expect(labels).toEqual(['Today', 'Yesterday', 'Older']);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('uses custom date field when specified', () => {
|
|
174
|
-
const now = new Date();
|
|
175
|
-
const items = [{ id: 1, updated_at: now.toISOString() }];
|
|
176
|
-
|
|
177
|
-
const result = groupByDate(items, 'updated_at');
|
|
178
|
-
|
|
179
|
-
expect(result).toHaveLength(1);
|
|
180
|
-
expect(result[0].label).toBe('Today');
|
|
99
|
+
const groups = groupByDate(items);
|
|
100
|
+
expect(groups.length).toBeGreaterThan(0);
|
|
101
|
+
expect(groups[0].label).toBe('Today');
|
|
181
102
|
});
|
|
182
103
|
|
|
183
104
|
it('returns empty array for empty input', () => {
|
|
184
|
-
const
|
|
185
|
-
expect(
|
|
105
|
+
const groups = groupByDate([]);
|
|
106
|
+
expect(groups).toEqual([]);
|
|
186
107
|
});
|
|
187
108
|
|
|
188
|
-
it('
|
|
109
|
+
it('uses custom date field', () => {
|
|
189
110
|
const now = new Date();
|
|
190
|
-
const items = [{ id: 1,
|
|
191
|
-
|
|
192
|
-
const result = groupByDate(items);
|
|
111
|
+
const items = [{ id: 1, updated_at: now.toISOString() }];
|
|
193
112
|
|
|
194
|
-
|
|
195
|
-
expect(
|
|
113
|
+
const groups = groupByDate(items, 'updated_at');
|
|
114
|
+
expect(groups.length).toBe(1);
|
|
115
|
+
expect(groups[0].label).toBe('Today');
|
|
196
116
|
});
|
|
197
117
|
});
|
|
198
118
|
});
|
package/src/styles/index.css
CHANGED
|
@@ -254,6 +254,45 @@ tr {
|
|
|
254
254
|
border-color: #12263f;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
.nodes-table-body tr:hover td {
|
|
258
|
+
background-color: rgba(59, 130, 246, 0.03);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Sortable table headers */
|
|
262
|
+
button.sortable {
|
|
263
|
+
background: none;
|
|
264
|
+
border: none;
|
|
265
|
+
cursor: pointer;
|
|
266
|
+
color: inherit;
|
|
267
|
+
padding: 0;
|
|
268
|
+
display: inline-flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
gap: 4px;
|
|
271
|
+
transition: color 0.15s ease;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
button.sortable:hover {
|
|
275
|
+
color: #1e293b;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
button.sortable::after {
|
|
279
|
+
content: '↕';
|
|
280
|
+
font-size: 12px;
|
|
281
|
+
opacity: 0.8;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
button.sortable.ascending::after {
|
|
285
|
+
content: '↑';
|
|
286
|
+
opacity: 1;
|
|
287
|
+
color: #3b82f6;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
button.sortable.descending::after {
|
|
291
|
+
content: '↓';
|
|
292
|
+
opacity: 1;
|
|
293
|
+
color: #3b82f6;
|
|
294
|
+
}
|
|
295
|
+
|
|
257
296
|
.card {
|
|
258
297
|
box-shadow: 0 0.75rem 1.5rem rgba(18, 38, 63, 0.03);
|
|
259
298
|
position: relative;
|
|
@@ -964,23 +1003,25 @@ pre {
|
|
|
964
1003
|
}
|
|
965
1004
|
|
|
966
1005
|
.select-name {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1006
|
+
display: flex;
|
|
1007
|
+
align-items: center;
|
|
1008
|
+
gap: 8px;
|
|
1009
|
+
padding: 8px 12px;
|
|
1010
|
+
cursor: pointer;
|
|
1011
|
+
user-select: none;
|
|
1012
|
+
transition: background 0.15s;
|
|
1013
|
+
font-size: 14px;
|
|
1014
|
+
font-weight: 400;
|
|
1015
|
+
color: #1e293b;
|
|
970
1016
|
border-radius: 4px;
|
|
971
|
-
cursor: default;
|
|
972
|
-
font-size: 0.9125rem;
|
|
973
|
-
color: #95aac9;
|
|
974
1017
|
}
|
|
975
1018
|
|
|
976
1019
|
.select-name:hover {
|
|
977
|
-
background-color: #
|
|
1020
|
+
background-color: #f1f5f9;
|
|
978
1021
|
}
|
|
979
1022
|
|
|
980
1023
|
.select-name-highlight {
|
|
981
|
-
|
|
982
|
-
background-color: #bdbaba3c;
|
|
983
|
-
border: 1px solid rgba(94, 94, 94, 0.24);
|
|
1024
|
+
background-color: #eef1f6;
|
|
984
1025
|
}
|
|
985
1026
|
|
|
986
1027
|
.inactive {
|