Merge pull request #2582 from github/robertbrignull/data-table
Update data extensions modelling table to new designs
This commit is contained in:
49
extensions/ql-vscode/src/view/common/Dropdown.tsx
Normal file
49
extensions/ql-vscode/src/view/common/Dropdown.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { ChangeEvent } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const StyledDropdown = styled.select`
|
||||||
|
width: 100%;
|
||||||
|
height: calc(var(--input-height) * 1px);
|
||||||
|
background: var(--vscode-dropdown-background);
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border: none;
|
||||||
|
padding: 2px 6px 2px 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: string | undefined;
|
||||||
|
options: Array<{ value: string; label: string }>;
|
||||||
|
disabled?: boolean;
|
||||||
|
onChange: (event: ChangeEvent<HTMLSelectElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dropdown implementation styled to look like `VSCodeDropdown`.
|
||||||
|
*
|
||||||
|
* The reason for doing this is that `VSCodeDropdown` doesn't handle fitting into
|
||||||
|
* available space and truncating content, and this leads to breaking the
|
||||||
|
* `VSCodeDataGrid` layout. This version using `select` directly will truncate the
|
||||||
|
* content as necessary and fit into whatever space is available.
|
||||||
|
* See https://github.com/github/vscode-codeql/pull/2582#issuecomment-1622164429
|
||||||
|
* for more info on the problem and other potential solutions.
|
||||||
|
*/
|
||||||
|
export function Dropdown({ value, options, disabled, onChange }: Props) {
|
||||||
|
return (
|
||||||
|
<StyledDropdown
|
||||||
|
value={disabled ? undefined : value}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={onChange}
|
||||||
|
>
|
||||||
|
{!disabled && (
|
||||||
|
<>
|
||||||
|
{options.map((option) => (
|
||||||
|
<option key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</StyledDropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,24 +1,25 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useCallback, useEffect } from "react";
|
import { ChangeEvent, useCallback, useEffect, useMemo } from "react";
|
||||||
import styled from "styled-components";
|
|
||||||
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
|
|
||||||
|
|
||||||
import type { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
import type { ModeledMethod } from "../../data-extensions-editor/modeled-method";
|
||||||
|
import { Dropdown } from "../common/Dropdown";
|
||||||
const Dropdown = styled(VSCodeDropdown)`
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
kinds: Array<ModeledMethod["kind"]>;
|
kinds: Array<ModeledMethod["kind"]>;
|
||||||
|
|
||||||
value: ModeledMethod["kind"] | undefined;
|
value: ModeledMethod["kind"] | undefined;
|
||||||
|
disabled?: boolean;
|
||||||
onChange: (value: ModeledMethod["kind"]) => void;
|
onChange: (value: ModeledMethod["kind"]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const KindInput = ({ kinds, value, onChange }: Props) => {
|
export const KindInput = ({ kinds, value, disabled, onChange }: Props) => {
|
||||||
|
const options = useMemo(
|
||||||
|
() => kinds.map((kind) => ({ value: kind, label: kind })),
|
||||||
|
[kinds],
|
||||||
|
);
|
||||||
|
|
||||||
const handleInput = useCallback(
|
const handleInput = useCallback(
|
||||||
(e: InputEvent) => {
|
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
const target = e.target as HTMLSelectElement;
|
const target = e.target as HTMLSelectElement;
|
||||||
|
|
||||||
onChange(target.value as ModeledMethod["kind"]);
|
onChange(target.value as ModeledMethod["kind"]);
|
||||||
@@ -37,12 +38,11 @@ export const KindInput = ({ kinds, value, onChange }: Props) => {
|
|||||||
}, [value, kinds, onChange]);
|
}, [value, kinds, onChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown value={value} onInput={handleInput}>
|
<Dropdown
|
||||||
{kinds.map((kind) => (
|
value={value}
|
||||||
<VSCodeOption key={kind} value={kind}>
|
options={options}
|
||||||
{kind}
|
disabled={disabled}
|
||||||
</VSCodeOption>
|
onChange={handleInput}
|
||||||
))}
|
/>
|
||||||
</Dropdown>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
|
VSCodeCheckbox,
|
||||||
VSCodeDataGridCell,
|
VSCodeDataGridCell,
|
||||||
VSCodeDataGridRow,
|
VSCodeDataGridRow,
|
||||||
VSCodeDropdown,
|
VSCodeLink,
|
||||||
VSCodeOption,
|
|
||||||
} from "@vscode/webview-ui-toolkit/react";
|
} from "@vscode/webview-ui-toolkit/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useCallback, useMemo } from "react";
|
import { ChangeEvent, useCallback, useMemo } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { vscode } from "../vscode-api";
|
import { vscode } from "../vscode-api";
|
||||||
|
|
||||||
@@ -18,52 +18,27 @@ import {
|
|||||||
import { KindInput } from "./KindInput";
|
import { KindInput } from "./KindInput";
|
||||||
import { extensiblePredicateDefinitions } from "../../data-extensions-editor/predicates";
|
import { extensiblePredicateDefinitions } from "../../data-extensions-editor/predicates";
|
||||||
import { Mode } from "../../data-extensions-editor/shared/mode";
|
import { Mode } from "../../data-extensions-editor/shared/mode";
|
||||||
|
import { Dropdown } from "../common/Dropdown";
|
||||||
|
|
||||||
const Dropdown = styled(VSCodeDropdown)`
|
const ApiOrMethodCell = styled(VSCodeDataGridCell)`
|
||||||
width: 100%;
|
display: flex;
|
||||||
`;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
type SupportedUnsupportedSpanProps = {
|
gap: 0.5em;
|
||||||
supported: boolean;
|
|
||||||
modeled: ModeledMethod | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SupportSpan = styled.span<SupportedUnsupportedSpanProps>`
|
|
||||||
color: ${(props) => {
|
|
||||||
if (!props.supported && props.modeled && props.modeled?.type !== "none") {
|
|
||||||
return "orange";
|
|
||||||
} else {
|
|
||||||
return props.supported ? "green" : "red";
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type SupportedUnsupportedLinkProps = {
|
|
||||||
supported: boolean;
|
|
||||||
modeled: ModeledMethod | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SupportLink = styled.button<SupportedUnsupportedLinkProps>`
|
|
||||||
color: ${(props) => {
|
|
||||||
if (!props.supported && props.modeled && props.modeled?.type !== "none") {
|
|
||||||
return "orange";
|
|
||||||
} else {
|
|
||||||
return props.supported ? "green" : "red";
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const UsagesButton = styled.button`
|
const UsagesButton = styled.button`
|
||||||
color: var(--vscode-editor-foreground);
|
color: var(--vscode-editor-foreground);
|
||||||
background-color: transparent;
|
background-color: var(--vscode-input-background);
|
||||||
border: none;
|
border: none;
|
||||||
|
border-radius: 40%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const ViewLink = styled(VSCodeLink)`
|
||||||
|
white-space: nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
externalApiUsage: ExternalApiUsage;
|
externalApiUsage: ExternalApiUsage;
|
||||||
modeledMethod: ModeledMethod | undefined;
|
modeledMethod: ModeledMethod | undefined;
|
||||||
@@ -90,9 +65,7 @@ export const MethodRow = ({
|
|||||||
}, [externalApiUsage.methodParameters]);
|
}, [externalApiUsage.methodParameters]);
|
||||||
|
|
||||||
const handleTypeInput = useCallback(
|
const handleTypeInput = useCallback(
|
||||||
(e: InputEvent) => {
|
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
const target = e.target as HTMLSelectElement;
|
|
||||||
|
|
||||||
let newProvenance: Provenance = "manual";
|
let newProvenance: Provenance = "manual";
|
||||||
if (modeledMethod?.provenance === "df-generated") {
|
if (modeledMethod?.provenance === "df-generated") {
|
||||||
newProvenance = "df-manual";
|
newProvenance = "df-manual";
|
||||||
@@ -106,14 +79,14 @@ export const MethodRow = ({
|
|||||||
output: "ReturnType",
|
output: "ReturnType",
|
||||||
kind: "value",
|
kind: "value",
|
||||||
...modeledMethod,
|
...modeledMethod,
|
||||||
type: target.value as ModeledMethodType,
|
type: e.target.value as ModeledMethodType,
|
||||||
provenance: newProvenance,
|
provenance: newProvenance,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[onChange, externalApiUsage, modeledMethod, argumentsList],
|
[onChange, externalApiUsage, modeledMethod, argumentsList],
|
||||||
);
|
);
|
||||||
const handleInputInput = useCallback(
|
const handleInputInput = useCallback(
|
||||||
(e: InputEvent) => {
|
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
if (!modeledMethod) {
|
if (!modeledMethod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -128,7 +101,7 @@ export const MethodRow = ({
|
|||||||
[onChange, externalApiUsage, modeledMethod],
|
[onChange, externalApiUsage, modeledMethod],
|
||||||
);
|
);
|
||||||
const handleOutputInput = useCallback(
|
const handleOutputInput = useCallback(
|
||||||
(e: InputEvent) => {
|
(e: ChangeEvent<HTMLSelectElement>) => {
|
||||||
if (!modeledMethod) {
|
if (!modeledMethod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -169,94 +142,96 @@ export const MethodRow = ({
|
|||||||
? extensiblePredicateDefinitions[modeledMethod.type]
|
? extensiblePredicateDefinitions[modeledMethod.type]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const showModelTypeCell =
|
||||||
|
!externalApiUsage.supported ||
|
||||||
|
(modeledMethod && modeledMethod?.type !== "none");
|
||||||
|
const modelTypeOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ value: "none", label: "Unmodeled" },
|
||||||
|
{ value: "source", label: "Source" },
|
||||||
|
{ value: "sink", label: "Sink" },
|
||||||
|
{ value: "summary", label: "Flow summary" },
|
||||||
|
{ value: "neutral", label: "Neutral" },
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const showInputCell =
|
||||||
|
modeledMethod?.type && ["sink", "summary"].includes(modeledMethod?.type);
|
||||||
|
const inputOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ value: "Argument[this]", label: "Argument[this]" },
|
||||||
|
...argumentsList.map((argument, index) => ({
|
||||||
|
value: `Argument[${index}]`,
|
||||||
|
label: `Argument[${index}]: ${argument}`,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[argumentsList],
|
||||||
|
);
|
||||||
|
|
||||||
|
const showOutputCell =
|
||||||
|
modeledMethod?.type && ["source", "summary"].includes(modeledMethod?.type);
|
||||||
|
const outputOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{ value: "ReturnValue", label: "ReturnValue" },
|
||||||
|
{ value: "Argument[this]", label: "Argument[this]" },
|
||||||
|
...argumentsList.map((argument, index) => ({
|
||||||
|
value: `Argument[${index}]`,
|
||||||
|
label: `Argument[${index}]: ${argument}`,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
[argumentsList],
|
||||||
|
);
|
||||||
|
|
||||||
|
const showKindCell = predicate?.supportedKinds;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VSCodeDataGridRow>
|
<VSCodeDataGridRow>
|
||||||
<VSCodeDataGridCell gridColumn={1}>
|
<ApiOrMethodCell gridColumn={1}>
|
||||||
<SupportSpan
|
<VSCodeCheckbox />
|
||||||
supported={externalApiUsage.supported}
|
<span>
|
||||||
modeled={modeledMethod}
|
{externalApiUsage.packageName}.{externalApiUsage.typeName}.
|
||||||
>
|
{externalApiUsage.methodName}
|
||||||
{externalApiUsage.packageName}.{externalApiUsage.typeName}
|
{externalApiUsage.methodParameters}
|
||||||
</SupportSpan>
|
</span>
|
||||||
</VSCodeDataGridCell>
|
|
||||||
<VSCodeDataGridCell gridColumn={2}>
|
|
||||||
{mode === Mode.Application && (
|
{mode === Mode.Application && (
|
||||||
<SupportSpan
|
|
||||||
supported={externalApiUsage.supported}
|
|
||||||
modeled={modeledMethod}
|
|
||||||
>
|
|
||||||
{externalApiUsage.methodName}
|
|
||||||
{externalApiUsage.methodParameters}
|
|
||||||
</SupportSpan>
|
|
||||||
)}
|
|
||||||
{mode === Mode.Framework && (
|
|
||||||
<SupportLink
|
|
||||||
supported={externalApiUsage.supported}
|
|
||||||
modeled={modeledMethod}
|
|
||||||
onClick={jumpToUsage}
|
|
||||||
>
|
|
||||||
{externalApiUsage.methodName}
|
|
||||||
{externalApiUsage.methodParameters}
|
|
||||||
</SupportLink>
|
|
||||||
)}
|
|
||||||
</VSCodeDataGridCell>
|
|
||||||
{mode === Mode.Application && (
|
|
||||||
<VSCodeDataGridCell gridColumn={3}>
|
|
||||||
<UsagesButton onClick={jumpToUsage}>
|
<UsagesButton onClick={jumpToUsage}>
|
||||||
{externalApiUsage.usages.length}
|
{externalApiUsage.usages.length}
|
||||||
</UsagesButton>
|
</UsagesButton>
|
||||||
</VSCodeDataGridCell>
|
|
||||||
)}
|
|
||||||
<VSCodeDataGridCell gridColumn={4}>
|
|
||||||
{(!externalApiUsage.supported ||
|
|
||||||
(modeledMethod && modeledMethod?.type !== "none")) && (
|
|
||||||
<Dropdown
|
|
||||||
value={modeledMethod?.type ?? "none"}
|
|
||||||
onInput={handleTypeInput}
|
|
||||||
>
|
|
||||||
<VSCodeOption value="none">Unmodeled</VSCodeOption>
|
|
||||||
<VSCodeOption value="source">Source</VSCodeOption>
|
|
||||||
<VSCodeOption value="sink">Sink</VSCodeOption>
|
|
||||||
<VSCodeOption value="summary">Flow summary</VSCodeOption>
|
|
||||||
<VSCodeOption value="neutral">Neutral</VSCodeOption>
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
)}
|
||||||
|
<ViewLink onClick={jumpToUsage}>View</ViewLink>
|
||||||
|
</ApiOrMethodCell>
|
||||||
|
<VSCodeDataGridCell gridColumn={2}>
|
||||||
|
<Dropdown
|
||||||
|
value={modeledMethod?.type ?? "none"}
|
||||||
|
options={modelTypeOptions}
|
||||||
|
disabled={!showModelTypeCell}
|
||||||
|
onChange={handleTypeInput}
|
||||||
|
/>
|
||||||
|
</VSCodeDataGridCell>
|
||||||
|
<VSCodeDataGridCell gridColumn={3}>
|
||||||
|
<Dropdown
|
||||||
|
value={modeledMethod?.input}
|
||||||
|
options={inputOptions}
|
||||||
|
disabled={!showInputCell}
|
||||||
|
onChange={handleInputInput}
|
||||||
|
/>
|
||||||
|
</VSCodeDataGridCell>
|
||||||
|
<VSCodeDataGridCell gridColumn={4}>
|
||||||
|
<Dropdown
|
||||||
|
value={modeledMethod?.output}
|
||||||
|
options={outputOptions}
|
||||||
|
disabled={!showOutputCell}
|
||||||
|
onChange={handleOutputInput}
|
||||||
|
/>
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
<VSCodeDataGridCell gridColumn={5}>
|
<VSCodeDataGridCell gridColumn={5}>
|
||||||
{modeledMethod?.type &&
|
<KindInput
|
||||||
["sink", "summary"].includes(modeledMethod?.type) && (
|
kinds={predicate?.supportedKinds || []}
|
||||||
<Dropdown value={modeledMethod?.input} onInput={handleInputInput}>
|
value={modeledMethod?.kind}
|
||||||
<VSCodeOption value="Argument[this]">Argument[this]</VSCodeOption>
|
disabled={!showKindCell}
|
||||||
{argumentsList.map((argument, index) => (
|
onChange={handleKindChange}
|
||||||
<VSCodeOption key={argument} value={`Argument[${index}]`}>
|
/>
|
||||||
Argument[{index}]: {argument}
|
|
||||||
</VSCodeOption>
|
|
||||||
))}
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
|
||||||
</VSCodeDataGridCell>
|
|
||||||
<VSCodeDataGridCell gridColumn={6}>
|
|
||||||
{modeledMethod?.type &&
|
|
||||||
["source", "summary"].includes(modeledMethod?.type) && (
|
|
||||||
<Dropdown value={modeledMethod?.output} onInput={handleOutputInput}>
|
|
||||||
<VSCodeOption value="ReturnValue">ReturnValue</VSCodeOption>
|
|
||||||
<VSCodeOption value="Argument[this]">Argument[this]</VSCodeOption>
|
|
||||||
{argumentsList.map((argument, index) => (
|
|
||||||
<VSCodeOption key={argument} value={`Argument[${index}]`}>
|
|
||||||
Argument[{index}]: {argument}
|
|
||||||
</VSCodeOption>
|
|
||||||
))}
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
|
||||||
</VSCodeDataGridCell>
|
|
||||||
<VSCodeDataGridCell gridColumn={7}>
|
|
||||||
{predicate?.supportedKinds && (
|
|
||||||
<KindInput
|
|
||||||
kinds={predicate.supportedKinds}
|
|
||||||
value={modeledMethod?.kind}
|
|
||||||
onChange={handleKindChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
</VSCodeDataGridRow>
|
</VSCodeDataGridRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,29 +33,21 @@ export const ModeledMethodDataGrid = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VSCodeDataGrid>
|
<VSCodeDataGrid gridTemplateColumns="0.4fr 0.15fr 0.15fr 0.15fr 0.15fr">
|
||||||
<VSCodeDataGridRow rowType="header">
|
<VSCodeDataGridRow rowType="header">
|
||||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={1}>
|
<VSCodeDataGridCell cellType="columnheader" gridColumn={1}>
|
||||||
Type
|
API or method
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={2}>
|
<VSCodeDataGridCell cellType="columnheader" gridColumn={2}>
|
||||||
Method
|
|
||||||
</VSCodeDataGridCell>
|
|
||||||
{mode === Mode.Application && (
|
|
||||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={3}>
|
|
||||||
Usages
|
|
||||||
</VSCodeDataGridCell>
|
|
||||||
)}
|
|
||||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={4}>
|
|
||||||
Model type
|
Model type
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={5}>
|
<VSCodeDataGridCell cellType="columnheader" gridColumn={3}>
|
||||||
Input
|
Input
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={6}>
|
<VSCodeDataGridCell cellType="columnheader" gridColumn={4}>
|
||||||
Output
|
Output
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
<VSCodeDataGridCell cellType="columnheader" gridColumn={7}>
|
<VSCodeDataGridCell cellType="columnheader" gridColumn={5}>
|
||||||
Kind
|
Kind
|
||||||
</VSCodeDataGridCell>
|
</VSCodeDataGridCell>
|
||||||
</VSCodeDataGridRow>
|
</VSCodeDataGridRow>
|
||||||
|
|||||||
Reference in New Issue
Block a user