Skip to content

Commit 261f11e

Browse files
Merge pull request #2582 from github/robertbrignull/data-table
Update data extensions modelling table to new designs
2 parents 7a54b00 + a07e829 commit 261f11e

File tree

4 files changed

+169
-153
lines changed

4 files changed

+169
-153
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as React from "react";
2+
import { ChangeEvent } from "react";
3+
import styled from "styled-components";
4+
5+
const StyledDropdown = styled.select`
6+
width: 100%;
7+
height: calc(var(--input-height) * 1px);
8+
background: var(--vscode-dropdown-background);
9+
color: var(--vscode-foreground);
10+
border: none;
11+
padding: 2px 6px 2px 8px;
12+
`;
13+
14+
type Props = {
15+
value: string | undefined;
16+
options: Array<{ value: string; label: string }>;
17+
disabled?: boolean;
18+
onChange: (event: ChangeEvent<HTMLSelectElement>) => void;
19+
};
20+
21+
/**
22+
* A dropdown implementation styled to look like `VSCodeDropdown`.
23+
*
24+
* The reason for doing this is that `VSCodeDropdown` doesn't handle fitting into
25+
* available space and truncating content, and this leads to breaking the
26+
* `VSCodeDataGrid` layout. This version using `select` directly will truncate the
27+
* content as necessary and fit into whatever space is available.
28+
* See https://github.com/github/vscode-codeql/pull/2582#issuecomment-1622164429
29+
* for more info on the problem and other potential solutions.
30+
*/
31+
export function Dropdown({ value, options, disabled, onChange }: Props) {
32+
return (
33+
<StyledDropdown
34+
value={disabled ? undefined : value}
35+
disabled={disabled}
36+
onChange={onChange}
37+
>
38+
{!disabled && (
39+
<>
40+
{options.map((option) => (
41+
<option key={option.value} value={option.value}>
42+
{option.label}
43+
</option>
44+
))}
45+
</>
46+
)}
47+
</StyledDropdown>
48+
);
49+
}
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
import * as React from "react";
2-
import { useCallback, useEffect } from "react";
3-
import styled from "styled-components";
4-
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
2+
import { ChangeEvent, useCallback, useEffect, useMemo } from "react";
53

64
import type { ModeledMethod } from "../../data-extensions-editor/modeled-method";
7-
8-
const Dropdown = styled(VSCodeDropdown)`
9-
width: 100%;
10-
`;
5+
import { Dropdown } from "../common/Dropdown";
116

127
type Props = {
138
kinds: Array<ModeledMethod["kind"]>;
149

1510
value: ModeledMethod["kind"] | undefined;
11+
disabled?: boolean;
1612
onChange: (value: ModeledMethod["kind"]) => void;
1713
};
1814

19-
export const KindInput = ({ kinds, value, onChange }: Props) => {
15+
export const KindInput = ({ kinds, value, disabled, onChange }: Props) => {
16+
const options = useMemo(
17+
() => kinds.map((kind) => ({ value: kind, label: kind })),
18+
[kinds],
19+
);
20+
2021
const handleInput = useCallback(
21-
(e: InputEvent) => {
22+
(e: ChangeEvent<HTMLSelectElement>) => {
2223
const target = e.target as HTMLSelectElement;
2324

2425
onChange(target.value as ModeledMethod["kind"]);
@@ -37,12 +38,11 @@ export const KindInput = ({ kinds, value, onChange }: Props) => {
3738
}, [value, kinds, onChange]);
3839

3940
return (
40-
<Dropdown value={value} onInput={handleInput}>
41-
{kinds.map((kind) => (
42-
<VSCodeOption key={kind} value={kind}>
43-
{kind}
44-
</VSCodeOption>
45-
))}
46-
</Dropdown>
41+
<Dropdown
42+
value={value}
43+
options={options}
44+
disabled={disabled}
45+
onChange={handleInput}
46+
/>
4747
);
4848
};

extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx

Lines changed: 99 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {
2+
VSCodeCheckbox,
23
VSCodeDataGridCell,
34
VSCodeDataGridRow,
4-
VSCodeDropdown,
5-
VSCodeOption,
5+
VSCodeLink,
66
} from "@vscode/webview-ui-toolkit/react";
77
import * as React from "react";
8-
import { useCallback, useMemo } from "react";
8+
import { ChangeEvent, useCallback, useMemo } from "react";
99
import styled from "styled-components";
1010
import { vscode } from "../vscode-api";
1111

@@ -18,52 +18,27 @@ import {
1818
import { KindInput } from "./KindInput";
1919
import { extensiblePredicateDefinitions } from "../../data-extensions-editor/predicates";
2020
import { Mode } from "../../data-extensions-editor/shared/mode";
21+
import { Dropdown } from "../common/Dropdown";
2122

22-
const Dropdown = styled(VSCodeDropdown)`
23-
width: 100%;
24-
`;
25-
26-
type SupportedUnsupportedSpanProps = {
27-
supported: boolean;
28-
modeled: ModeledMethod | undefined;
29-
};
30-
31-
const SupportSpan = styled.span<SupportedUnsupportedSpanProps>`
32-
color: ${(props) => {
33-
if (!props.supported && props.modeled && props.modeled?.type !== "none") {
34-
return "orange";
35-
} else {
36-
return props.supported ? "green" : "red";
37-
}
38-
}};
39-
`;
40-
41-
type SupportedUnsupportedLinkProps = {
42-
supported: boolean;
43-
modeled: ModeledMethod | undefined;
44-
};
45-
46-
const SupportLink = styled.button<SupportedUnsupportedLinkProps>`
47-
color: ${(props) => {
48-
if (!props.supported && props.modeled && props.modeled?.type !== "none") {
49-
return "orange";
50-
} else {
51-
return props.supported ? "green" : "red";
52-
}
53-
}};
54-
background-color: transparent;
55-
border: none;
56-
cursor: pointer;
57-
padding: 0;
23+
const ApiOrMethodCell = styled(VSCodeDataGridCell)`
24+
display: flex;
25+
flex-direction: row;
26+
align-items: center;
27+
gap: 0.5em;
5828
`;
5929

6030
const UsagesButton = styled.button`
6131
color: var(--vscode-editor-foreground);
62-
background-color: transparent;
32+
background-color: var(--vscode-input-background);
6333
border: none;
34+
border-radius: 40%;
6435
cursor: pointer;
6536
`;
6637

38+
const ViewLink = styled(VSCodeLink)`
39+
white-space: nowrap;
40+
`;
41+
6742
type Props = {
6843
externalApiUsage: ExternalApiUsage;
6944
modeledMethod: ModeledMethod | undefined;
@@ -90,9 +65,7 @@ export const MethodRow = ({
9065
}, [externalApiUsage.methodParameters]);
9166

9267
const handleTypeInput = useCallback(
93-
(e: InputEvent) => {
94-
const target = e.target as HTMLSelectElement;
95-
68+
(e: ChangeEvent<HTMLSelectElement>) => {
9669
let newProvenance: Provenance = "manual";
9770
if (modeledMethod?.provenance === "df-generated") {
9871
newProvenance = "df-manual";
@@ -106,14 +79,14 @@ export const MethodRow = ({
10679
output: "ReturnType",
10780
kind: "value",
10881
...modeledMethod,
109-
type: target.value as ModeledMethodType,
82+
type: e.target.value as ModeledMethodType,
11083
provenance: newProvenance,
11184
});
11285
},
11386
[onChange, externalApiUsage, modeledMethod, argumentsList],
11487
);
11588
const handleInputInput = useCallback(
116-
(e: InputEvent) => {
89+
(e: ChangeEvent<HTMLSelectElement>) => {
11790
if (!modeledMethod) {
11891
return;
11992
}
@@ -128,7 +101,7 @@ export const MethodRow = ({
128101
[onChange, externalApiUsage, modeledMethod],
129102
);
130103
const handleOutputInput = useCallback(
131-
(e: InputEvent) => {
104+
(e: ChangeEvent<HTMLSelectElement>) => {
132105
if (!modeledMethod) {
133106
return;
134107
}
@@ -169,94 +142,96 @@ export const MethodRow = ({
169142
? extensiblePredicateDefinitions[modeledMethod.type]
170143
: undefined;
171144

145+
const showModelTypeCell =
146+
!externalApiUsage.supported ||
147+
(modeledMethod && modeledMethod?.type !== "none");
148+
const modelTypeOptions = useMemo(
149+
() => [
150+
{ value: "none", label: "Unmodeled" },
151+
{ value: "source", label: "Source" },
152+
{ value: "sink", label: "Sink" },
153+
{ value: "summary", label: "Flow summary" },
154+
{ value: "neutral", label: "Neutral" },
155+
],
156+
[],
157+
);
158+
159+
const showInputCell =
160+
modeledMethod?.type && ["sink", "summary"].includes(modeledMethod?.type);
161+
const inputOptions = useMemo(
162+
() => [
163+
{ value: "Argument[this]", label: "Argument[this]" },
164+
...argumentsList.map((argument, index) => ({
165+
value: `Argument[${index}]`,
166+
label: `Argument[${index}]: ${argument}`,
167+
})),
168+
],
169+
[argumentsList],
170+
);
171+
172+
const showOutputCell =
173+
modeledMethod?.type && ["source", "summary"].includes(modeledMethod?.type);
174+
const outputOptions = useMemo(
175+
() => [
176+
{ value: "ReturnValue", label: "ReturnValue" },
177+
{ value: "Argument[this]", label: "Argument[this]" },
178+
...argumentsList.map((argument, index) => ({
179+
value: `Argument[${index}]`,
180+
label: `Argument[${index}]: ${argument}`,
181+
})),
182+
],
183+
[argumentsList],
184+
);
185+
186+
const showKindCell = predicate?.supportedKinds;
187+
172188
return (
173189
<VSCodeDataGridRow>
174-
<VSCodeDataGridCell gridColumn={1}>
175-
<SupportSpan
176-
supported={externalApiUsage.supported}
177-
modeled={modeledMethod}
178-
>
179-
{externalApiUsage.packageName}.{externalApiUsage.typeName}
180-
</SupportSpan>
181-
</VSCodeDataGridCell>
182-
<VSCodeDataGridCell gridColumn={2}>
190+
<ApiOrMethodCell gridColumn={1}>
191+
<VSCodeCheckbox />
192+
<span>
193+
{externalApiUsage.packageName}.{externalApiUsage.typeName}.
194+
{externalApiUsage.methodName}
195+
{externalApiUsage.methodParameters}
196+
</span>
183197
{mode === Mode.Application && (
184-
<SupportSpan
185-
supported={externalApiUsage.supported}
186-
modeled={modeledMethod}
187-
>
188-
{externalApiUsage.methodName}
189-
{externalApiUsage.methodParameters}
190-
</SupportSpan>
191-
)}
192-
{mode === Mode.Framework && (
193-
<SupportLink
194-
supported={externalApiUsage.supported}
195-
modeled={modeledMethod}
196-
onClick={jumpToUsage}
197-
>
198-
{externalApiUsage.methodName}
199-
{externalApiUsage.methodParameters}
200-
</SupportLink>
201-
)}
202-
</VSCodeDataGridCell>
203-
{mode === Mode.Application && (
204-
<VSCodeDataGridCell gridColumn={3}>
205198
<UsagesButton onClick={jumpToUsage}>
206199
{externalApiUsage.usages.length}
207200
</UsagesButton>
208-
</VSCodeDataGridCell>
209-
)}
210-
<VSCodeDataGridCell gridColumn={4}>
211-
{(!externalApiUsage.supported ||
212-
(modeledMethod && modeledMethod?.type !== "none")) && (
213-
<Dropdown
214-
value={modeledMethod?.type ?? "none"}
215-
onInput={handleTypeInput}
216-
>
217-
<VSCodeOption value="none">Unmodeled</VSCodeOption>
218-
<VSCodeOption value="source">Source</VSCodeOption>
219-
<VSCodeOption value="sink">Sink</VSCodeOption>
220-
<VSCodeOption value="summary">Flow summary</VSCodeOption>
221-
<VSCodeOption value="neutral">Neutral</VSCodeOption>
222-
</Dropdown>
223201
)}
202+
<ViewLink onClick={jumpToUsage}>View</ViewLink>
203+
</ApiOrMethodCell>
204+
<VSCodeDataGridCell gridColumn={2}>
205+
<Dropdown
206+
value={modeledMethod?.type ?? "none"}
207+
options={modelTypeOptions}
208+
disabled={!showModelTypeCell}
209+
onChange={handleTypeInput}
210+
/>
224211
</VSCodeDataGridCell>
225-
<VSCodeDataGridCell gridColumn={5}>
226-
{modeledMethod?.type &&
227-
["sink", "summary"].includes(modeledMethod?.type) && (
228-
<Dropdown value={modeledMethod?.input} onInput={handleInputInput}>
229-
<VSCodeOption value="Argument[this]">Argument[this]</VSCodeOption>
230-
{argumentsList.map((argument, index) => (
231-
<VSCodeOption key={argument} value={`Argument[${index}]`}>
232-
Argument[{index}]: {argument}
233-
</VSCodeOption>
234-
))}
235-
</Dropdown>
236-
)}
212+
<VSCodeDataGridCell gridColumn={3}>
213+
<Dropdown
214+
value={modeledMethod?.input}
215+
options={inputOptions}
216+
disabled={!showInputCell}
217+
onChange={handleInputInput}
218+
/>
237219
</VSCodeDataGridCell>
238-
<VSCodeDataGridCell gridColumn={6}>
239-
{modeledMethod?.type &&
240-
["source", "summary"].includes(modeledMethod?.type) && (
241-
<Dropdown value={modeledMethod?.output} onInput={handleOutputInput}>
242-
<VSCodeOption value="ReturnValue">ReturnValue</VSCodeOption>
243-
<VSCodeOption value="Argument[this]">Argument[this]</VSCodeOption>
244-
{argumentsList.map((argument, index) => (
245-
<VSCodeOption key={argument} value={`Argument[${index}]`}>
246-
Argument[{index}]: {argument}
247-
</VSCodeOption>
248-
))}
249-
</Dropdown>
250-
)}
220+
<VSCodeDataGridCell gridColumn={4}>
221+
<Dropdown
222+
value={modeledMethod?.output}
223+
options={outputOptions}
224+
disabled={!showOutputCell}
225+
onChange={handleOutputInput}
226+
/>
251227
</VSCodeDataGridCell>
252-
<VSCodeDataGridCell gridColumn={7}>
253-
{predicate?.supportedKinds && (
254-
<KindInput
255-
kinds={predicate.supportedKinds}
256-
value={modeledMethod?.kind}
257-
onChange={handleKindChange}
258-
/>
259-
)}
228+
<VSCodeDataGridCell gridColumn={5}>
229+
<KindInput
230+
kinds={predicate?.supportedKinds || []}
231+
value={modeledMethod?.kind}
232+
disabled={!showKindCell}
233+
onChange={handleKindChange}
234+
/>
260235
</VSCodeDataGridCell>
261236
</VSCodeDataGridRow>
262237
);

0 commit comments

Comments
 (0)