datajunction-ui 0.0.31 → 0.0.34
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/TODO.md +265 -0
- package/package.json +1 -1
- package/src/app/components/ListGroupItem.jsx +2 -2
- package/src/app/components/QueryInfo.jsx +2 -1
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +2 -2
- package/src/app/components/djgraph/__tests__/Collapse.test.jsx +6 -3
- package/src/app/pages/AddEditNodePage/index.jsx +1 -1
- package/src/app/pages/AddEditTagPage/index.jsx +1 -1
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +55 -21
- package/src/app/pages/NodePage/NodeInfoTab.jsx +17 -6
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +5 -0
- package/src/app/pages/NodePage/NodePreAggregationsTab.jsx +656 -0
- package/src/app/pages/NodePage/NodeValidateTab.jsx +4 -2
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +58 -45
- package/src/app/pages/NodePage/__tests__/NodePreAggregationsTab.test.jsx +654 -0
- package/src/app/pages/NodePage/index.jsx +9 -1
- package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +19 -4
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +47 -9
- package/src/app/pages/SQLBuilderPage/index.jsx +2 -2
- package/src/app/services/DJService.js +26 -0
- package/src/styles/preaggregations.css +547 -0
|
@@ -62,14 +62,23 @@ describe('<NotificationsPage />', () => {
|
|
|
62
62
|
const mockDjClient = createMockDjClient();
|
|
63
63
|
renderWithContext(mockDjClient);
|
|
64
64
|
|
|
65
|
+
// Wait for async effects to complete
|
|
66
|
+
await waitFor(() => {
|
|
67
|
+
expect(mockDjClient.getSubscribedHistory).toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
65
70
|
expect(screen.getByText('Notifications')).toBeInTheDocument();
|
|
66
71
|
});
|
|
67
72
|
|
|
68
|
-
it('shows loading state initially', () => {
|
|
73
|
+
it('shows loading state initially', async () => {
|
|
74
|
+
// Use a controlled promise that we can resolve after the test
|
|
75
|
+
let resolvePromise;
|
|
76
|
+
const pendingPromise = new Promise(resolve => {
|
|
77
|
+
resolvePromise = resolve;
|
|
78
|
+
});
|
|
79
|
+
|
|
69
80
|
const mockDjClient = createMockDjClient({
|
|
70
|
-
getSubscribedHistory: jest.fn().mockImplementation(
|
|
71
|
-
() => new Promise(() => {}), // Never resolves
|
|
72
|
-
),
|
|
81
|
+
getSubscribedHistory: jest.fn().mockImplementation(() => pendingPromise),
|
|
73
82
|
});
|
|
74
83
|
renderWithContext(mockDjClient);
|
|
75
84
|
|
|
@@ -78,6 +87,12 @@ describe('<NotificationsPage />', () => {
|
|
|
78
87
|
'[style*="text-align: center"]',
|
|
79
88
|
);
|
|
80
89
|
expect(loadingContainer).toBeInTheDocument();
|
|
90
|
+
|
|
91
|
+
// Resolve the promise to allow cleanup without act() warnings
|
|
92
|
+
resolvePromise([]);
|
|
93
|
+
await waitFor(() => {
|
|
94
|
+
expect(mockDjClient.getSubscribedHistory).toHaveBeenCalled();
|
|
95
|
+
});
|
|
81
96
|
});
|
|
82
97
|
|
|
83
98
|
it('shows empty state when no notifications', async () => {
|
|
@@ -114,31 +114,65 @@ describe('SQLBuilderPage', () => {
|
|
|
114
114
|
mockDjClient.commonDimensions.mockResolvedValue(mockCommonDimensions);
|
|
115
115
|
mockDjClient.sqls.mockResolvedValue({ sql: 'SELECT ...' });
|
|
116
116
|
mockDjClient.data.mockResolvedValue({});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
jest.clearAllMocks();
|
|
121
|
+
});
|
|
117
122
|
|
|
123
|
+
it('renders without crashing', async () => {
|
|
118
124
|
render(
|
|
119
125
|
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
120
126
|
<SQLBuilderPage />
|
|
121
127
|
</DJClientContext.Provider>,
|
|
122
128
|
);
|
|
123
|
-
});
|
|
124
129
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
132
|
+
});
|
|
128
133
|
|
|
129
|
-
it('renders without crashing', () => {
|
|
130
134
|
expect(screen.getByText('Using the SQL Builder')).toBeInTheDocument();
|
|
131
135
|
});
|
|
132
136
|
|
|
133
|
-
it('renders the Metrics section', () => {
|
|
137
|
+
it('renders the Metrics section', async () => {
|
|
138
|
+
render(
|
|
139
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
140
|
+
<SQLBuilderPage />
|
|
141
|
+
</DJClientContext.Provider>,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
134
148
|
expect(screen.getByText('Metrics')).toBeInTheDocument();
|
|
135
149
|
});
|
|
136
150
|
|
|
137
|
-
it('renders the Group By section', () => {
|
|
151
|
+
it('renders the Group By section', async () => {
|
|
152
|
+
render(
|
|
153
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
154
|
+
<SQLBuilderPage />
|
|
155
|
+
</DJClientContext.Provider>,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
138
162
|
expect(screen.getByText('Group By')).toBeInTheDocument();
|
|
139
163
|
});
|
|
140
164
|
|
|
141
|
-
it('renders the Filter By section', () => {
|
|
165
|
+
it('renders the Filter By section', async () => {
|
|
166
|
+
render(
|
|
167
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
168
|
+
<SQLBuilderPage />
|
|
169
|
+
</DJClientContext.Provider>,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
await waitFor(() => {
|
|
173
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
174
|
+
});
|
|
175
|
+
|
|
142
176
|
expect(screen.getByText('Filter By')).toBeInTheDocument();
|
|
143
177
|
});
|
|
144
178
|
|
|
@@ -179,7 +213,11 @@ describe('SQLBuilderPage', () => {
|
|
|
179
213
|
expect(screen.getAllByText(dim.name)[0]).toBeInTheDocument();
|
|
180
214
|
fireEvent.click(screen.getAllByText(dim.name)[0]);
|
|
181
215
|
}
|
|
182
|
-
|
|
216
|
+
|
|
217
|
+
// Wait for SQL fetch to complete to avoid act() warnings
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
expect(mockDjClient.sqls).toHaveBeenCalled();
|
|
220
|
+
});
|
|
183
221
|
});
|
|
184
222
|
});
|
|
185
223
|
|
|
@@ -181,8 +181,8 @@ export function SQLBuilderPage() {
|
|
|
181
181
|
setDisplayedRows(
|
|
182
182
|
data[0]?.rows.slice(0, showNumRows).map((rowData, index) => (
|
|
183
183
|
<tr key={`data-row:${index}`}>
|
|
184
|
-
{rowData.map(rowValue => (
|
|
185
|
-
<td key={
|
|
184
|
+
{rowData.map((rowValue, colIndex) => (
|
|
185
|
+
<td key={`${index}-${colIndex}`}>{rowValue}</td>
|
|
186
186
|
))}
|
|
187
187
|
</tr>
|
|
188
188
|
)),
|
|
@@ -1895,6 +1895,7 @@ export const DataJunctionAPI = {
|
|
|
1895
1895
|
if (filters.grain_mode) params.append('grain_mode', filters.grain_mode);
|
|
1896
1896
|
if (filters.measures) params.append('measures', filters.measures);
|
|
1897
1897
|
if (filters.status) params.append('status', filters.status);
|
|
1898
|
+
if (filters.include_stale) params.append('include_stale', 'true');
|
|
1898
1899
|
|
|
1899
1900
|
return await (
|
|
1900
1901
|
await fetch(`${DJ_URL}/preaggs/?${params}`, {
|
|
@@ -2050,6 +2051,31 @@ export const DataJunctionAPI = {
|
|
|
2050
2051
|
return result;
|
|
2051
2052
|
},
|
|
2052
2053
|
|
|
2054
|
+
// Bulk deactivate pre-aggregation workflows for a node
|
|
2055
|
+
bulkDeactivatePreaggWorkflows: async function (nodeName, staleOnly = false) {
|
|
2056
|
+
const params = new URLSearchParams();
|
|
2057
|
+
params.append('node_name', nodeName);
|
|
2058
|
+
if (staleOnly) params.append('stale_only', 'true');
|
|
2059
|
+
|
|
2060
|
+
const response = await fetch(`${DJ_URL}/preaggs/workflows?${params}`, {
|
|
2061
|
+
method: 'DELETE',
|
|
2062
|
+
credentials: 'include',
|
|
2063
|
+
});
|
|
2064
|
+
const result = await response.json();
|
|
2065
|
+
if (!response.ok) {
|
|
2066
|
+
return {
|
|
2067
|
+
...result,
|
|
2068
|
+
_error: true,
|
|
2069
|
+
_status: response.status,
|
|
2070
|
+
message:
|
|
2071
|
+
result.message ||
|
|
2072
|
+
result.detail ||
|
|
2073
|
+
'Failed to bulk deactivate workflows',
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
return result;
|
|
2077
|
+
},
|
|
2078
|
+
|
|
2053
2079
|
// Get cube details including materializations
|
|
2054
2080
|
getCubeDetails: async function (cubeName) {
|
|
2055
2081
|
const response = await fetch(`${DJ_URL}/cubes/${cubeName}`, {
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-aggregations Tab Styles
|
|
3
|
+
*
|
|
4
|
+
* Reusable CSS classes for the pre-aggregations UI components.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* =============================================================================
|
|
8
|
+
Layout
|
|
9
|
+
============================================================================= */
|
|
10
|
+
|
|
11
|
+
.preagg-container {
|
|
12
|
+
padding: 10px 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.preagg-section {
|
|
16
|
+
margin-bottom: 30px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.preagg-two-column {
|
|
20
|
+
display: grid;
|
|
21
|
+
grid-template-columns: 1fr 1fr;
|
|
22
|
+
gap: 16px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.preagg-stack {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
gap: 1.5em;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* =============================================================================
|
|
32
|
+
Section Headers
|
|
33
|
+
============================================================================= */
|
|
34
|
+
|
|
35
|
+
.preagg-section-header {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
margin-bottom: 16px;
|
|
39
|
+
border-bottom: 2px solid #e5e7eb;
|
|
40
|
+
padding-bottom: 8px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.preagg-section-header--stale {
|
|
44
|
+
border-bottom-color: #fcd34d;
|
|
45
|
+
justify-content: space-between;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.preagg-section-title {
|
|
49
|
+
margin: 0;
|
|
50
|
+
font-size: 16px;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
color: #374151;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.preagg-section-title--stale {
|
|
56
|
+
color: #92400e;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.preagg-section-count {
|
|
60
|
+
margin-left: 12px;
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
color: #6b7280;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.preagg-section-count--stale {
|
|
66
|
+
color: #b45309;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* =============================================================================
|
|
70
|
+
Pre-agg Row (Card Container)
|
|
71
|
+
============================================================================= */
|
|
72
|
+
|
|
73
|
+
.preagg-row {
|
|
74
|
+
border: 1px solid #e0e0e0;
|
|
75
|
+
border-radius: 8px;
|
|
76
|
+
margin-bottom: 10px;
|
|
77
|
+
background-color: #fff;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.preagg-row--stale {
|
|
81
|
+
background-color: #fffbeb;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Collapsed Header */
|
|
85
|
+
.preagg-row-header {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
padding: 12px 16px;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
gap: 12px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.preagg-row-toggle {
|
|
94
|
+
font-size: 14px;
|
|
95
|
+
color: #666;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.preagg-row-grain-chips {
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 6px;
|
|
102
|
+
min-width: 180px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.preagg-grain-chip {
|
|
106
|
+
padding: 2px 8px;
|
|
107
|
+
background-color: #f1f5f9;
|
|
108
|
+
border-radius: 4px;
|
|
109
|
+
color: #475569;
|
|
110
|
+
font-size: 12px;
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
font-family: monospace;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.preagg-grain-chip--more {
|
|
116
|
+
background-color: #e2e8f0;
|
|
117
|
+
color: #64748b;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.preagg-row-measures {
|
|
121
|
+
font-size: 12px;
|
|
122
|
+
color: #563a12;
|
|
123
|
+
background: #fff6e9;
|
|
124
|
+
border-radius: 8px;
|
|
125
|
+
padding: 2px 8px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.preagg-row-schedule {
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
color: #888;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.preagg-row-version {
|
|
134
|
+
font-size: 12px;
|
|
135
|
+
color: #b45309;
|
|
136
|
+
font-style: italic;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Expanded Details */
|
|
140
|
+
.preagg-details {
|
|
141
|
+
padding: 20px;
|
|
142
|
+
border-top: 1px solid #e0e0e0;
|
|
143
|
+
background-color: #f8fafc;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.preagg-details--stale {
|
|
147
|
+
background-color: #fefce8;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* =============================================================================
|
|
151
|
+
Stale Warning Banner
|
|
152
|
+
============================================================================= */
|
|
153
|
+
|
|
154
|
+
.preagg-stale-banner {
|
|
155
|
+
background-color: #fef3c7;
|
|
156
|
+
border: 1px solid #fcd34d;
|
|
157
|
+
border-radius: 8px;
|
|
158
|
+
padding: 12px 16px;
|
|
159
|
+
margin-bottom: 20px;
|
|
160
|
+
font-size: 13px;
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
gap: 10px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.preagg-stale-banner-icon {
|
|
167
|
+
font-size: 18px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.preagg-stale-banner-text {
|
|
171
|
+
color: #78350f;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* =============================================================================
|
|
175
|
+
Card Boxes (Config, Grain, etc.)
|
|
176
|
+
============================================================================= */
|
|
177
|
+
|
|
178
|
+
.preagg-card {
|
|
179
|
+
background-color: #ffffff;
|
|
180
|
+
border-radius: 8px;
|
|
181
|
+
/* border: 1px solid #e2e8f0; */
|
|
182
|
+
padding: 16px;
|
|
183
|
+
height: fit-content;
|
|
184
|
+
box-sizing: border-box;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.preagg-card--compact {
|
|
188
|
+
padding: 12px 16px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.preagg-card-label {
|
|
192
|
+
font-size: 12px;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
color: #64748b;
|
|
195
|
+
text-transform: uppercase;
|
|
196
|
+
letter-spacing: 0.05em;
|
|
197
|
+
margin-bottom: 8px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.preagg-card-label--with-info {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 6px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* =============================================================================
|
|
207
|
+
Config Table
|
|
208
|
+
============================================================================= */
|
|
209
|
+
|
|
210
|
+
.preagg-config-table {
|
|
211
|
+
font-size: 13px;
|
|
212
|
+
border-collapse: collapse;
|
|
213
|
+
width: 100%;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.preagg-config-key {
|
|
217
|
+
padding: 4px 12px 4px 0;
|
|
218
|
+
color: #64748b;
|
|
219
|
+
font-weight: 500;
|
|
220
|
+
white-space: nowrap;
|
|
221
|
+
width: 100px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.preagg-config-value {
|
|
225
|
+
padding: 4px 0;
|
|
226
|
+
color: #1e293b;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.preagg-config-value code {
|
|
230
|
+
font-size: 12px;
|
|
231
|
+
background-color: #f1f5f9;
|
|
232
|
+
padding: 2px 6px;
|
|
233
|
+
border-radius: 4px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.preagg-config-schedule-cron {
|
|
237
|
+
margin-left: 6px;
|
|
238
|
+
font-size: 11px;
|
|
239
|
+
color: #94a3b8;
|
|
240
|
+
font-family: monospace;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* =============================================================================
|
|
244
|
+
Actions
|
|
245
|
+
============================================================================= */
|
|
246
|
+
|
|
247
|
+
.preagg-actions {
|
|
248
|
+
display: flex;
|
|
249
|
+
flex-wrap: wrap;
|
|
250
|
+
gap: 8px;
|
|
251
|
+
padding-top: 12px;
|
|
252
|
+
margin-top: 12px;
|
|
253
|
+
border-top: 1px solid #e2e8f0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.preagg-action-btn {
|
|
257
|
+
display: inline-flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
padding: 5px 10px;
|
|
260
|
+
background-color: #ffffff;
|
|
261
|
+
border: 1px solid #e2e8f0;
|
|
262
|
+
border-radius: 6px;
|
|
263
|
+
color: #475569;
|
|
264
|
+
font-size: 12px;
|
|
265
|
+
font-weight: 500;
|
|
266
|
+
text-decoration: none;
|
|
267
|
+
cursor: pointer;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.preagg-action-btn:hover {
|
|
271
|
+
background-color: #f8fafc;
|
|
272
|
+
text-decoration: none;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.preagg-action-btn--danger {
|
|
276
|
+
border-color: #fecaca;
|
|
277
|
+
color: #dc2626;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.preagg-action-btn--danger:hover {
|
|
281
|
+
background-color: #fef2f2;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.preagg-action-btn--danger-fill {
|
|
285
|
+
background-color: #fee2e2;
|
|
286
|
+
border-color: #fca5a5;
|
|
287
|
+
color: #991b1b;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.preagg-action-btn:disabled {
|
|
291
|
+
cursor: not-allowed;
|
|
292
|
+
opacity: 0.7;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* =============================================================================
|
|
296
|
+
Badges
|
|
297
|
+
============================================================================= */
|
|
298
|
+
|
|
299
|
+
/* Base badge */
|
|
300
|
+
.preagg-badge {
|
|
301
|
+
padding: 4px 10px;
|
|
302
|
+
border-radius: 4px;
|
|
303
|
+
font-size: 12px;
|
|
304
|
+
font-weight: 500;
|
|
305
|
+
text-decoration: none;
|
|
306
|
+
display: inline-block;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* Status badges (pill style) */
|
|
310
|
+
.preagg-status-badge {
|
|
311
|
+
padding: 2px 8px;
|
|
312
|
+
border-radius: 12px;
|
|
313
|
+
font-size: 12px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.preagg-status-badge--active {
|
|
317
|
+
background-color: #dcfce7;
|
|
318
|
+
color: #166534;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.preagg-status-badge--paused {
|
|
322
|
+
background-color: #fef3c7;
|
|
323
|
+
color: #92400e;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.preagg-status-badge--pending {
|
|
327
|
+
background-color: #f3f4f6;
|
|
328
|
+
color: #6b7280;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* Metric count badge (in header row) */
|
|
332
|
+
.preagg-metric-count-badge {
|
|
333
|
+
font-size: 12px;
|
|
334
|
+
color: #be123c;
|
|
335
|
+
background-color: #fff1f2;
|
|
336
|
+
padding: 2px 8px;
|
|
337
|
+
border-radius: 12px;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* Grain badge */
|
|
341
|
+
.preagg-grain-badge,
|
|
342
|
+
.preagg-grain-badge:hover {
|
|
343
|
+
padding: 4px 10px;
|
|
344
|
+
background-color: #f1f5f9;
|
|
345
|
+
border-radius: 4px;
|
|
346
|
+
color: #1e40af;
|
|
347
|
+
font-size: 12px;
|
|
348
|
+
font-weight: 500;
|
|
349
|
+
text-decoration: none;
|
|
350
|
+
font-family: monospace;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.preagg-grain-badge:hover {
|
|
354
|
+
background-color: #e2e8f0;
|
|
355
|
+
text-decoration: none;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* Aggregation badge (blue) */
|
|
359
|
+
.preagg-agg-badge {
|
|
360
|
+
background-color: #dbeafe;
|
|
361
|
+
padding: 4px 10px;
|
|
362
|
+
border-radius: 4px;
|
|
363
|
+
color: #1e40af;
|
|
364
|
+
font-size: 12px;
|
|
365
|
+
font-weight: 500;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* Merge badge (green) */
|
|
369
|
+
.preagg-merge-badge {
|
|
370
|
+
background-color: #dcfce7;
|
|
371
|
+
padding: 4px 10px;
|
|
372
|
+
border-radius: 4px;
|
|
373
|
+
color: #166534;
|
|
374
|
+
font-size: 12px;
|
|
375
|
+
font-weight: 500;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* Rule badge (gray) */
|
|
379
|
+
.preagg-rule-badge {
|
|
380
|
+
color: #475569;
|
|
381
|
+
background-color: #f1f5f9;
|
|
382
|
+
padding: 4px 8px;
|
|
383
|
+
border-radius: 4px;
|
|
384
|
+
font-size: 11px;
|
|
385
|
+
font-weight: 500;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* Metric badge (red/rose) */
|
|
389
|
+
.preagg-metric-badge {
|
|
390
|
+
font-size: 11px;
|
|
391
|
+
color: #be123c;
|
|
392
|
+
background-color: #fff1f2;
|
|
393
|
+
padding: 3px 8px;
|
|
394
|
+
border-radius: 4px;
|
|
395
|
+
text-decoration: none;
|
|
396
|
+
border: 1px solid #fecdd3;
|
|
397
|
+
font-weight: 500;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.preagg-metric-badge:hover {
|
|
401
|
+
background-color: #ffe4e6;
|
|
402
|
+
text-decoration: none;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* Expand/collapse button (used in grain section) */
|
|
406
|
+
.preagg-expand-btn {
|
|
407
|
+
padding: 4px 10px;
|
|
408
|
+
background-color: #f1f5f9;
|
|
409
|
+
border-radius: 4px;
|
|
410
|
+
color: #64748b;
|
|
411
|
+
font-size: 12px;
|
|
412
|
+
font-weight: 500;
|
|
413
|
+
border: none;
|
|
414
|
+
cursor: pointer;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.preagg-expand-btn:hover {
|
|
418
|
+
background-color: #e2e8f0;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* =============================================================================
|
|
422
|
+
Grain List
|
|
423
|
+
============================================================================= */
|
|
424
|
+
|
|
425
|
+
.preagg-grain-list {
|
|
426
|
+
display: flex;
|
|
427
|
+
flex-wrap: wrap;
|
|
428
|
+
gap: 8px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* =============================================================================
|
|
432
|
+
Measures Table
|
|
433
|
+
============================================================================= */
|
|
434
|
+
|
|
435
|
+
.preagg-measures-table {
|
|
436
|
+
width: 100%;
|
|
437
|
+
font-size: 13px;
|
|
438
|
+
border-collapse: collapse;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.preagg-measures-table thead {
|
|
442
|
+
background-color: #fafafa;
|
|
443
|
+
border-bottom: 1px solid #e2e8f0;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.preagg-measures-table th {
|
|
447
|
+
padding: 10px 16px;
|
|
448
|
+
text-align: left;
|
|
449
|
+
font-weight: 500;
|
|
450
|
+
color: #64748b;
|
|
451
|
+
font-size: 12px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.preagg-measures-table td {
|
|
455
|
+
padding: 12px 16px;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.preagg-measures-table tbody tr {
|
|
459
|
+
border-bottom: 1px solid #f1f5f9;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.preagg-measures-table tbody tr:last-child {
|
|
463
|
+
border-bottom: none;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.preagg-measure-name {
|
|
467
|
+
font-weight: 500;
|
|
468
|
+
color: #1e293b;
|
|
469
|
+
font-family: monospace;
|
|
470
|
+
font-size: 12px;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.preagg-metrics-list {
|
|
474
|
+
display: flex;
|
|
475
|
+
flex-wrap: wrap;
|
|
476
|
+
gap: 6px;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* =============================================================================
|
|
480
|
+
Info Icon
|
|
481
|
+
============================================================================= */
|
|
482
|
+
|
|
483
|
+
.preagg-info-icon {
|
|
484
|
+
cursor: help;
|
|
485
|
+
color: #94a3b8;
|
|
486
|
+
font-weight: normal;
|
|
487
|
+
margin-left: 4px;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* =============================================================================
|
|
491
|
+
Empty State
|
|
492
|
+
============================================================================= */
|
|
493
|
+
|
|
494
|
+
.preagg-empty {
|
|
495
|
+
padding: 16px;
|
|
496
|
+
background-color: #f9fafb;
|
|
497
|
+
border-radius: 8px;
|
|
498
|
+
color: #6b7280;
|
|
499
|
+
font-size: 14px;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* =============================================================================
|
|
503
|
+
Loading & Error States
|
|
504
|
+
============================================================================= */
|
|
505
|
+
|
|
506
|
+
.preagg-loading {
|
|
507
|
+
padding: 20px;
|
|
508
|
+
text-align: center;
|
|
509
|
+
color: #666;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.preagg-error {
|
|
513
|
+
padding: 20px;
|
|
514
|
+
margin: 20px 0;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.preagg-no-data {
|
|
518
|
+
padding: 20px;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.preagg-no-data-alert {
|
|
522
|
+
margin-bottom: 20px;
|
|
523
|
+
padding: 16px;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.preagg-no-data-text {
|
|
527
|
+
font-size: 14px;
|
|
528
|
+
color: #666;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/* =============================================================================
|
|
532
|
+
Section Header (Stale section - left side)
|
|
533
|
+
============================================================================= */
|
|
534
|
+
|
|
535
|
+
.preagg-section-header-left {
|
|
536
|
+
display: flex;
|
|
537
|
+
align-items: center;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/* =============================================================================
|
|
541
|
+
Card Modifier for Tables
|
|
542
|
+
============================================================================= */
|
|
543
|
+
|
|
544
|
+
.preagg-card--table {
|
|
545
|
+
padding: 0;
|
|
546
|
+
overflow: hidden;
|
|
547
|
+
}
|