Skip to content

Commit 012d75d

Browse files
committed
add warning icon when base table is missing
1 parent 9104ea6 commit 012d75d

File tree

6 files changed

+133
-45
lines changed

6 files changed

+133
-45
lines changed

packages/browser-tests/cypress/commands.js

+21
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,19 @@ Cypress.Commands.add("dropTable", (name) => {
248248
});
249249
});
250250

251+
Cypress.Commands.add("dropTableIfExists", (name) => {
252+
const authHeader = localStorage.getItem("basic.auth.header");
253+
cy.request({
254+
method: "GET",
255+
url: `${baseUrl}/exec?query=${encodeURIComponent(
256+
`DROP TABLE IF EXISTS ${name};`
257+
)}`,
258+
headers: {
259+
Authorization: authHeader,
260+
},
261+
});
262+
});
263+
251264
Cypress.Commands.add("dropMaterializedView", (name) => {
252265
const authHeader = localStorage.getItem("basic.auth.header");
253266
cy.request({
@@ -345,6 +358,14 @@ Cypress.Commands.add("expandMatViews", () => {
345358
});
346359
});
347360

361+
Cypress.Commands.add("collapseMatViews", () => {
362+
cy.get("body").then((body) => {
363+
if (body.find('[data-hook="collapse-materialized-views"]').length > 0) {
364+
cy.get('[data-hook="collapse-materialized-views"]').click();
365+
}
366+
});
367+
});
368+
348369
Cypress.Commands.add("getEditorTabs", () => {
349370
return cy.get(".chrome-tab");
350371
});

packages/browser-tests/cypress/integration/console/schema.spec.js

+36-11
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,15 @@ describe("materialized views", () => {
227227
cy.refreshSchema();
228228
});
229229

230+
afterEach(() => {
231+
cy.collapseTables();
232+
cy.collapseMatViews();
233+
});
234+
230235
it("should create materialized views", () => {
231236
cy.getByDataHook("expand-tables").contains(`Tables (${tables.length})`);
232237
cy.getByDataHook("expand-materialized-views").contains(
233-
`Materialized Views (${materializedViews.length})`
238+
`Materialized views (${materializedViews.length})`
234239
);
235240

236241
cy.expandTables();
@@ -240,19 +245,39 @@ describe("materialized views", () => {
240245
});
241246

242247
it("should show the base table and copy DDL for a materialized view", () => {
243-
cy.collapseTables();
248+
cy.expandMatViews();
244249
cy.getByDataHook("schema-table-title").contains("btc_trades_mv").click();
245-
cy.getByDataHook("schema-info-title").contains("DDL").should("exist");
250+
cy.getByDataHook("schema-info-title").contains("Query").should("exist");
246251
cy.getByDataHook("copyable-value").should("exist").and("be.visible");
247252
cy.getByDataHook("copy-value").should("exist").click({ force: true });
248253

249-
cy.window()
250-
.its("navigator.clipboard")
251-
.invoke("readText")
252-
.should(
253-
"match",
254-
/^CREATE MATERIALIZED VIEW.*'btc_trades_mv' WITH BASE 'btc_trades'/
255-
);
254+
if (Cypress.isBrowser("electron")) {
255+
cy.window()
256+
.its("navigator.clipboard")
257+
.invoke("readText")
258+
.should(
259+
"match",
260+
/^CREATE MATERIALIZED VIEW.*'btc_trades_mv' WITH BASE 'btc_trades'/
261+
);
262+
}
263+
});
264+
265+
it("should show a warning icon when base table is dropped", () => {
266+
cy.dropTable("btc_trades");
267+
cy.wait(1000);
268+
cy.refreshSchema();
269+
270+
cy.expandMatViews();
271+
cy.getByDataHook("schema-table-title").contains("btc_trades_mv").click();
272+
273+
cy.get('[data-hook="base-table-warning"]')
274+
.should("exist")
275+
.trigger("mouseover");
276+
277+
cy.getByDataHook("tooltip").should(
278+
"contain",
279+
"Base table has been dropped"
280+
);
256281
});
257282

258283
after(() => {
@@ -263,7 +288,7 @@ describe("materialized views", () => {
263288
});
264289

265290
tables.forEach((table) => {
266-
cy.dropTable(table);
291+
cy.dropTableIfExists(table);
267292
});
268293
});
269294
});

packages/web-console/src/components/CopyButton/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const CopyButton = ({
2626
return (
2727
<StyledButton
2828
skin="secondary"
29+
data-hook="copy-value"
2930
onClick={(e) => {
3031
navigator.clipboard.writeText(text)
3132
e.stopPropagation()

packages/web-console/src/scenes/Schema/Row/index.tsx

+24-29
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import * as QuestDB from "../../../utils/questdb"
3333
import Highlighter from "react-highlight-words"
3434
import { TableIcon } from "../table-icon"
3535
import { Box } from "@questdb/react-components"
36-
import { CopyButton } from '../../../components/CopyButton'
3736
import { Text, TransitionDuration, IconWithTooltip, spinAnimation } from "../../../components"
3837
import type { TextProps } from "../../../components"
3938
import { color } from "../../../utils"
@@ -64,9 +63,12 @@ type Props = Readonly<{
6463
selected?: boolean
6564
onSelectToggle?: (table_name: string) => void
6665
copyable?: boolean
66+
suffix?: React.ReactNode
6767
}>
6868

6969
const Type = styled(Text)`
70+
display: flex;
71+
align-items: center;
7072
margin-right: 1rem;
7173
flex: 0;
7274
transition: opacity ${TransitionDuration.REG}ms;
@@ -165,22 +167,8 @@ const ValueWrapper = styled.div`
165167
gap: 0.5rem;
166168
max-width: 200px;
167169
flex: 1;
168-
`
169-
170-
const CopyButtonWrapper = styled.div`
171-
flex: 1;
172-
display: flex;
173-
174-
button {
175-
border: 0;
176-
flex: 1;
177-
height: 100%;
178-
padding: 0.3rem 0.3rem;
179-
180-
svg {
181-
height: 1.3rem;
182-
}
183-
}
170+
justify-content: flex-end;
171+
width: 100%;
184172
`
185173

186174
const TruncatedBox = styled(Box)`
@@ -191,6 +179,7 @@ const TruncatedBox = styled(Box)`
191179
cursor: default;
192180
font-style: italic;
193181
color: ${color("gray2")};
182+
text-align: right;
194183
`
195184

196185
const Loader = styled(Loader4)`
@@ -199,6 +188,15 @@ const Loader = styled(Loader4)`
199188
${spinAnimation};
200189
`
201190

191+
const ValueText = styled(Text)`
192+
font-style: italic;
193+
color: ${color("gray2")};
194+
flex: 1;
195+
text-align: right;
196+
overflow: hidden;
197+
white-space: nowrap;
198+
`
199+
202200
const Row = ({
203201
className,
204202
designatedTimestamp,
@@ -220,6 +218,7 @@ const Row = ({
220218
selected,
221219
onSelectToggle,
222220
copyable,
221+
suffix,
223222
}: Props) => {
224223
const { query } = useContext(SchemaContext)
225224
const [showLoader, setShowLoader] = useState(false)
@@ -330,23 +329,19 @@ const Row = ({
330329
)}
331330

332331
{kind === 'info' && value && (
333-
copyable ? (
334-
<ValueWrapper>
332+
<ValueWrapper>
333+
{copyable ? (
335334
<PopperHover
336335
placement="top"
337336
trigger={<TruncatedBox data-hook="copyable-value">{value}</TruncatedBox>}
338337
>
339338
<Tooltip>{value}</Tooltip>
340339
</PopperHover>
341-
<CopyButtonWrapper>
342-
<CopyButton text={value} iconOnly={true} />
343-
</CopyButtonWrapper>
344-
</ValueWrapper>
345-
) : (
346-
<Type _style="italic" color="gray2">
347-
{value}
348-
</Type>
349-
)
340+
) : (
341+
<ValueText>{value}</ValueText>
342+
)}
343+
{suffix}
344+
</ValueWrapper>
350345
)}
351346

352347
{walTableData?.suspended && kind === "table" && (
@@ -367,7 +362,7 @@ const Row = ({
367362
/>
368363
)}
369364
</FlexRow>
370-
{!tooltip && !copyable && <Text color="comment">{description}</Text>}
365+
{!tooltip && kind !== 'info' && <Text color="comment">{description}</Text>}
371366
</Box>
372367
</Wrapper>
373368
)

packages/web-console/src/scenes/Schema/Table/index.tsx

+41
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import React, { useContext, useEffect, useState } from "react"
2626
import styled from "styled-components"
27+
import { ErrorWarning } from '@styled-icons/remix-line'
2728
import { Tree, collapseTransition } from "../../../components"
2829
import { TreeNode, TreeNodeRenderParams, Text } from "../../../components"
2930
import { ContextMenuTrigger } from "../../../components/ContextMenu"
@@ -34,6 +35,8 @@ import { useSelector, useDispatch } from "react-redux"
3435
import { actions, selectors } from "../../../store"
3536
import { QuestContext } from "../../../providers"
3637
import { NotificationType } from "../../../types"
38+
import { CopyButton } from '../../../components/CopyButton'
39+
import { IconWithTooltip } from '../../../components'
3740

3841
type Props = QuestDB.Table &
3942
Readonly<{
@@ -46,6 +49,7 @@ type Props = QuestDB.Table &
4649
onChange: (name: string) => void
4750
walTableData?: QuestDB.WalTable
4851
matViewData?: QuestDB.MaterializedView
52+
baseTableExists?: boolean
4953
selected: boolean
5054
selectOpen: boolean
5155
onSelectToggle: (table_name: string) => void
@@ -55,6 +59,7 @@ const Wrapper = styled.div`
5559
position: relative;
5660
display: flex;
5761
margin-top: 0.5rem;
62+
margin-bottom: 0.5rem;
5863
align-items: stretch;
5964
flex-direction: column;
6065
overflow: hidden;
@@ -90,6 +95,27 @@ const Columns = styled.div`
9095
}
9196
`
9297

98+
const SuffixWrapper = styled.div`
99+
display: flex;
100+
align-items: center;
101+
justify-content: space-between;
102+
103+
button {
104+
border: 0;
105+
height: 100%;
106+
padding: 0.3rem 0.3rem;
107+
108+
svg {
109+
height: 1.3rem;
110+
}
111+
}
112+
`
113+
114+
const WarningIcon = styled(ErrorWarning)`
115+
color: ${color("orange")};
116+
height: 1.9rem;
117+
`
118+
93119
const columnRender =
94120
({
95121
table_id,
@@ -130,6 +156,7 @@ const Table = ({
130156
selectOpen,
131157
onSelectToggle,
132158
matView,
159+
baseTableExists,
133160
}: Props) => {
134161
const { quest } = useContext(QuestContext)
135162
const [columns, setColumns] = useState<QuestDB.Column[]>()
@@ -244,6 +271,15 @@ const Table = ({
244271
kind="info"
245272
name="Base table"
246273
value={matViewData?.base_table_name}
274+
suffix={!baseTableExists && (
275+
<SuffixWrapper data-hook="base-table-warning">
276+
<IconWithTooltip
277+
icon={<WarningIcon />}
278+
placement="top"
279+
tooltip="Base table has been dropped"
280+
/>
281+
</SuffixWrapper>
282+
)}
247283
/>
248284
)
249285
})
@@ -259,6 +295,11 @@ const Table = ({
259295
name="Query"
260296
value={response.data[0].ddl}
261297
copyable={true}
298+
suffix={
299+
<SuffixWrapper>
300+
<CopyButton text={response.data[0].ddl} iconOnly={true} />
301+
</SuffixWrapper>
302+
}
262303
/>
263304
)
264305
})

packages/web-console/src/scenes/Schema/VirtualTables/index.tsx

+10-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type VirtualTablesProps = {
2626
const SectionHeader = styled.div`
2727
padding: 1rem;
2828
font-weight: bold;
29-
background: ${color("backgroundDarker")};
29+
background: ${color("selectionDarker")};
3030
display: flex;
3131
justify-content: space-between;
3232
align-items: center;
@@ -163,10 +163,17 @@ export const VirtualTables: FC<VirtualTablesProps> = ({
163163
return null
164164
}
165165
const table = allTables[index]
166-
166+
const matViewData = materializedViews?.find(
167+
(mv) => mv.view_name === table.table_name,
168+
)
169+
const baseTableExists = table.matView && matViewData
170+
? tables.some(t => t.table_name === matViewData.base_table_name)
171+
: false
172+
167173
return (
168174
<Table
169175
matView={table.matView}
176+
baseTableExists={baseTableExists}
170177
designatedTimestamp={table.designatedTimestamp}
171178
expanded={table.table_name === opened}
172179
key={table.table_name}
@@ -180,9 +187,7 @@ export const VirtualTables: FC<VirtualTablesProps> = ({
180187
walTableData={walTables?.find(
181188
(wt) => wt.name === table.table_name,
182189
)}
183-
matViewData={materializedViews?.find(
184-
(mv) => mv.view_name === table.table_name,
185-
)}
190+
matViewData={matViewData}
186191
dedup={table.dedup}
187192
selectOpen={selectOpen}
188193
selected={selectedTables.includes(table.table_name)}

0 commit comments

Comments
 (0)