Merge pull request #2582 from github/robertbrignull/data-table

Update data extensions modelling table to new designs
This commit is contained in:
Robert
2023-07-06 16:27:06 +01:00
committed by GitHub
4 changed files with 170 additions and 154 deletions

View 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>
);
}

View File

@@ -1,24 +1,25 @@
import * as React from "react";
import { useCallback, useEffect } from "react";
import styled from "styled-components";
import { VSCodeDropdown, VSCodeOption } from "@vscode/webview-ui-toolkit/react";
import { ChangeEvent, useCallback, useEffect, useMemo } from "react";
import type { ModeledMethod } from "../../data-extensions-editor/modeled-method";
const Dropdown = styled(VSCodeDropdown)`
width: 100%;
`;
import { Dropdown } from "../common/Dropdown";
type Props = {
kinds: Array<ModeledMethod["kind"]>;
value: ModeledMethod["kind"] | undefined;
disabled?: boolean;
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(
(e: InputEvent) => {
(e: ChangeEvent<HTMLSelectElement>) => {
const target = e.target as HTMLSelectElement;
onChange(target.value as ModeledMethod["kind"]);
@@ -37,12 +38,11 @@ export const KindInput = ({ kinds, value, onChange }: Props) => {
}, [value, kinds, onChange]);
return (
<Dropdown value={value} onInput={handleInput}>
{kinds.map((kind) => (
<VSCodeOption key={kind} value={kind}>
{kind}
</VSCodeOption>
))}
</Dropdown>
<Dropdown
value={value}
options={options}
disabled={disabled}
onChange={handleInput}
/>
);
};

View File

@@ -1,11 +1,11 @@
import {
VSCodeCheckbox,
VSCodeDataGridCell,
VSCodeDataGridRow,
VSCodeDropdown,
VSCodeOption,
VSCodeLink,
} from "@vscode/webview-ui-toolkit/react";
import * as React from "react";
import { useCallback, useMemo } from "react";
import { ChangeEvent, useCallback, useMemo } from "react";
import styled from "styled-components";
import { vscode } from "../vscode-api";
@@ -18,52 +18,27 @@ import {
import { KindInput } from "./KindInput";
import { extensiblePredicateDefinitions } from "../../data-extensions-editor/predicates";
import { Mode } from "../../data-extensions-editor/shared/mode";
import { Dropdown } from "../common/Dropdown";
const Dropdown = styled(VSCodeDropdown)`
width: 100%;
`;
type SupportedUnsupportedSpanProps = {
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 ApiOrMethodCell = styled(VSCodeDataGridCell)`
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5em;
`;
const UsagesButton = styled.button`
color: var(--vscode-editor-foreground);
background-color: transparent;
background-color: var(--vscode-input-background);
border: none;
border-radius: 40%;
cursor: pointer;
`;
const ViewLink = styled(VSCodeLink)`
white-space: nowrap;
`;
type Props = {
externalApiUsage: ExternalApiUsage;
modeledMethod: ModeledMethod | undefined;
@@ -90,9 +65,7 @@ export const MethodRow = ({
}, [externalApiUsage.methodParameters]);
const handleTypeInput = useCallback(
(e: InputEvent) => {
const target = e.target as HTMLSelectElement;
(e: ChangeEvent<HTMLSelectElement>) => {
let newProvenance: Provenance = "manual";
if (modeledMethod?.provenance === "df-generated") {
newProvenance = "df-manual";
@@ -106,14 +79,14 @@ export const MethodRow = ({
output: "ReturnType",
kind: "value",
...modeledMethod,
type: target.value as ModeledMethodType,
type: e.target.value as ModeledMethodType,
provenance: newProvenance,
});
},
[onChange, externalApiUsage, modeledMethod, argumentsList],
);
const handleInputInput = useCallback(
(e: InputEvent) => {
(e: ChangeEvent<HTMLSelectElement>) => {
if (!modeledMethod) {
return;
}
@@ -128,7 +101,7 @@ export const MethodRow = ({
[onChange, externalApiUsage, modeledMethod],
);
const handleOutputInput = useCallback(
(e: InputEvent) => {
(e: ChangeEvent<HTMLSelectElement>) => {
if (!modeledMethod) {
return;
}
@@ -169,94 +142,96 @@ export const MethodRow = ({
? extensiblePredicateDefinitions[modeledMethod.type]
: 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 (
<VSCodeDataGridRow>
<VSCodeDataGridCell gridColumn={1}>
<SupportSpan
supported={externalApiUsage.supported}
modeled={modeledMethod}
>
{externalApiUsage.packageName}.{externalApiUsage.typeName}
</SupportSpan>
</VSCodeDataGridCell>
<VSCodeDataGridCell gridColumn={2}>
<ApiOrMethodCell gridColumn={1}>
<VSCodeCheckbox />
<span>
{externalApiUsage.packageName}.{externalApiUsage.typeName}.
{externalApiUsage.methodName}
{externalApiUsage.methodParameters}
</span>
{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}>
{externalApiUsage.usages.length}
</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 gridColumn={5}>
{modeledMethod?.type &&
["sink", "summary"].includes(modeledMethod?.type) && (
<Dropdown value={modeledMethod?.input} onInput={handleInputInput}>
<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={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}
/>
)}
<KindInput
kinds={predicate?.supportedKinds || []}
value={modeledMethod?.kind}
disabled={!showKindCell}
onChange={handleKindChange}
/>
</VSCodeDataGridCell>
</VSCodeDataGridRow>
);

View File

@@ -33,29 +33,21 @@ export const ModeledMethodDataGrid = ({
);
return (
<VSCodeDataGrid>
<VSCodeDataGrid gridTemplateColumns="0.4fr 0.15fr 0.15fr 0.15fr 0.15fr">
<VSCodeDataGridRow rowType="header">
<VSCodeDataGridCell cellType="columnheader" gridColumn={1}>
Type
API or method
</VSCodeDataGridCell>
<VSCodeDataGridCell cellType="columnheader" gridColumn={2}>
Method
</VSCodeDataGridCell>
{mode === Mode.Application && (
<VSCodeDataGridCell cellType="columnheader" gridColumn={3}>
Usages
</VSCodeDataGridCell>
)}
<VSCodeDataGridCell cellType="columnheader" gridColumn={4}>
Model type
</VSCodeDataGridCell>
<VSCodeDataGridCell cellType="columnheader" gridColumn={5}>
<VSCodeDataGridCell cellType="columnheader" gridColumn={3}>
Input
</VSCodeDataGridCell>
<VSCodeDataGridCell cellType="columnheader" gridColumn={6}>
<VSCodeDataGridCell cellType="columnheader" gridColumn={4}>
Output
</VSCodeDataGridCell>
<VSCodeDataGridCell cellType="columnheader" gridColumn={7}>
<VSCodeDataGridCell cellType="columnheader" gridColumn={5}>
Kind
</VSCodeDataGridCell>
</VSCodeDataGridRow>