mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: clean up usage of Object.keys (#14484)
This commit is contained in:
committed by
GitHub
parent
d2a22c538b
commit
4672849d05
@@ -37,16 +37,12 @@ export const Timeline = <TData,>({
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.keys(itemsByDate).map((dateStr) => {
|
||||
const items = itemsByDate[dateStr];
|
||||
|
||||
return (
|
||||
<Fragment key={dateStr}>
|
||||
<TimelineDateRow date={new Date(dateStr)} />
|
||||
{items.map(row)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{Object.entries(itemsByDate).map(([dateStr, items]) => (
|
||||
<Fragment key={dateStr}>
|
||||
<TimelineDateRow date={new Date(dateStr)} />
|
||||
{items.map(row)}
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -233,21 +233,9 @@ const selectByLatency = (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const proxyMap = proxies.reduce(
|
||||
(acc, proxy) => {
|
||||
acc[proxy.id] = proxy;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, Region>,
|
||||
);
|
||||
|
||||
const best = Object.keys(latencies)
|
||||
.map((proxyId) => {
|
||||
return {
|
||||
id: proxyId,
|
||||
...latencies[proxyId],
|
||||
};
|
||||
})
|
||||
const proxyMap = Object.fromEntries(proxies.map((it) => [it.id, it]));
|
||||
const best = Object.entries(latencies)
|
||||
.map(([proxyId, latency]) => ({ id: proxyId, ...latency }))
|
||||
// If the proxy is not in our list, or it is unhealthy, ignore it.
|
||||
.filter((latency) => proxyMap[latency.id]?.healthy)
|
||||
.sort((a, b) => a.latencyMS - b.latencyMS)
|
||||
|
||||
@@ -13,12 +13,13 @@ export const getFeatureVisibility = (
|
||||
return {};
|
||||
}
|
||||
|
||||
const permissionPairs = Object.keys(features).map((feature) => {
|
||||
const { entitlement, limit, actual, enabled } = features[feature];
|
||||
const entitled = ["entitled", "grace_period"].includes(entitlement);
|
||||
const limitCompliant = limit && actual ? limit >= actual : true;
|
||||
return [feature, entitled && limitCompliant && enabled];
|
||||
});
|
||||
const permissionPairs = Object.entries(features).map(
|
||||
([feature, { entitlement, limit, actual, enabled }]) => {
|
||||
const entitled = ["entitled", "grace_period"].includes(entitlement);
|
||||
const limitCompliant = limit && actual ? limit >= actual : true;
|
||||
return [feature, entitled && limitCompliant && enabled];
|
||||
},
|
||||
);
|
||||
return Object.fromEntries(permissionPairs);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,17 +9,23 @@ import { DockerIcon } from "components/Icons/DockerIcon";
|
||||
import { type CSSProperties, type ElementType, type FC, useState } from "react";
|
||||
import type { FileTree } from "utils/filetree";
|
||||
|
||||
const sortFileTree = (fileTree: FileTree) => (a: string, b: string) => {
|
||||
const contentA = fileTree[a];
|
||||
const contentB = fileTree[b];
|
||||
if (typeof contentA === "object") {
|
||||
return -1;
|
||||
const isFolder = (content?: FileTree | string): content is FileTree =>
|
||||
typeof content === "object";
|
||||
|
||||
type FileTreeEntry = [key: string, content: FileTree | string];
|
||||
function compareFileTreeEntries(
|
||||
[keyA, contentA]: FileTreeEntry,
|
||||
[keyB, contentB]: FileTreeEntry,
|
||||
) {
|
||||
// A and B are either both files or both folders, so they should be sorted
|
||||
// lexically.
|
||||
if (isFolder(contentA) === isFolder(contentB)) {
|
||||
return keyA.localeCompare(keyB);
|
||||
}
|
||||
if (typeof contentB === "object") {
|
||||
return 1;
|
||||
}
|
||||
return a.localeCompare(b);
|
||||
};
|
||||
// Either A or B is a folder, and the other is a file. Put whichever one is a
|
||||
// folder first.
|
||||
return isFolder(contentA) ? -1 : 1;
|
||||
}
|
||||
|
||||
type ContextMenu = {
|
||||
path: string;
|
||||
@@ -51,9 +57,6 @@ export const TemplateFileTree: FC<TemplateFilesTreeProps> = ({
|
||||
}) => {
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenu | undefined>();
|
||||
|
||||
const isFolder = (content?: FileTree | string): content is FileTree =>
|
||||
typeof content === "object";
|
||||
|
||||
const buildTreeItems = (
|
||||
label: string,
|
||||
filename: string,
|
||||
@@ -181,12 +184,11 @@ export const TemplateFileTree: FC<TemplateFilesTreeProps> = ({
|
||||
}
|
||||
>
|
||||
{isFolder(content) &&
|
||||
Object.keys(content)
|
||||
.sort(sortFileTree(content))
|
||||
.map((filename) => {
|
||||
const child = content[filename];
|
||||
return buildTreeItems(filename, filename, child, currentPath);
|
||||
})}
|
||||
Object.entries(content)
|
||||
.sort(compareFileTreeEntries)
|
||||
.map(([filename, child]) =>
|
||||
buildTreeItems(filename, filename, child, currentPath),
|
||||
)}
|
||||
</TreeItem>
|
||||
);
|
||||
};
|
||||
@@ -198,12 +200,9 @@ export const TemplateFileTree: FC<TemplateFilesTreeProps> = ({
|
||||
defaultExpandedItems={activePath ? expandablePaths(activePath) : []}
|
||||
defaultSelectedItems={activePath}
|
||||
>
|
||||
{Object.keys(fileTree)
|
||||
.sort(sortFileTree(fileTree))
|
||||
.map((filename) => {
|
||||
const child = fileTree[filename];
|
||||
return buildTreeItems(filename, filename, child);
|
||||
})}
|
||||
{Object.entries(fileTree)
|
||||
.sort(compareFileTreeEntries)
|
||||
.map(([filename, child]) => buildTreeItems(filename, filename, child))}
|
||||
|
||||
<Menu
|
||||
onClose={() => setContextMenu(undefined)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import EditOutlined from "@mui/icons-material/EditOutlined";
|
||||
import RadioButtonCheckedOutlined from "@mui/icons-material/RadioButtonCheckedOutlined";
|
||||
import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter";
|
||||
import set from "lodash/fp/set";
|
||||
import set from "lodash/set";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
import { type FC, useCallback, useMemo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -31,8 +31,6 @@ export const TemplateFiles: FC<TemplateFilesProps> = ({
|
||||
const getLink = useLinks();
|
||||
const theme = useTheme();
|
||||
|
||||
const filenames = Object.keys(currentFiles);
|
||||
|
||||
const fileInfo = useCallback(
|
||||
(filename: string) => {
|
||||
const value = currentFiles[filename].trim();
|
||||
@@ -49,13 +47,13 @@ export const TemplateFiles: FC<TemplateFilesProps> = ({
|
||||
);
|
||||
|
||||
const fileTree: FileTree = useMemo(() => {
|
||||
let tree: FileTree = {};
|
||||
for (const filename of filenames) {
|
||||
const tree: FileTree = {};
|
||||
for (const filename of Object.keys(currentFiles)) {
|
||||
const info = fileInfo(filename);
|
||||
tree = set(filename.split("/"), info.value, tree);
|
||||
set(tree, filename.split("/"), info.value);
|
||||
}
|
||||
return tree;
|
||||
}, [fileInfo, filenames]);
|
||||
}, [fileInfo, currentFiles]);
|
||||
|
||||
const versionLink = `${getLink(
|
||||
linkToTemplate(organizationName, templateName),
|
||||
@@ -88,7 +86,7 @@ export const TemplateFiles: FC<TemplateFilesProps> = ({
|
||||
</div>
|
||||
|
||||
<div css={styles.files} data-testid="template-files-content">
|
||||
{[...filenames]
|
||||
{Object.keys(currentFiles)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((filename) => {
|
||||
const info = fileInfo(filename);
|
||||
|
||||
@@ -51,7 +51,6 @@ export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const groupedLogsByStage = groupLogsByStage(logs);
|
||||
const stages = Object.keys(groupedLogsByStage);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -62,8 +61,7 @@ export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({
|
||||
}}
|
||||
{...attrs}
|
||||
>
|
||||
{stages.map((stage) => {
|
||||
const logs = groupedLogsByStage[stage];
|
||||
{Object.entries(groupedLogsByStage).map(([stage, logs]) => {
|
||||
const isEmpty = logs.every((log) => log.output === "");
|
||||
const lines = logs.map((log) => ({
|
||||
time: log.created_at,
|
||||
|
||||
@@ -152,11 +152,9 @@ export const HealthLayout: FC = () => {
|
||||
</div>
|
||||
|
||||
<nav css={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
{Object.keys(visibleSections)
|
||||
{Object.entries(visibleSections)
|
||||
.sort()
|
||||
.map((key) => {
|
||||
const label =
|
||||
visibleSections[key as keyof typeof visibleSections];
|
||||
.map(([key, label]) => {
|
||||
const healthSection =
|
||||
healthStatus[key as keyof typeof visibleSections];
|
||||
|
||||
|
||||
@@ -60,15 +60,10 @@ export const ProvisionerDaemonsPage: FC = () => {
|
||||
const daemonScope = daemon.tags.scope || "organization";
|
||||
const iconScope =
|
||||
daemonScope === "organization" ? <Business /> : <Person />;
|
||||
const extraTags = Object.keys(daemon.tags)
|
||||
.filter((key) => key !== "scope" && key !== "owner")
|
||||
.reduce(
|
||||
(acc, key) => {
|
||||
acc[key] = daemon.tags[key];
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
|
||||
const extraTags = Object.entries(daemon.tags).filter(
|
||||
([key]) => key !== "scope" && key !== "owner",
|
||||
);
|
||||
const isWarning = warnings.length > 0;
|
||||
return (
|
||||
<div
|
||||
@@ -131,8 +126,8 @@ export const ProvisionerDaemonsPage: FC = () => {
|
||||
</span>
|
||||
</Pill>
|
||||
</Tooltip>
|
||||
{Object.keys(extraTags).map((k) => (
|
||||
<ProvisionerTag key={k} k={k} v={extraTags[k]} />
|
||||
{extraTags.map(([key, value]) => (
|
||||
<ProvisionerTag key={key} tagName={key} tagValue={value} />
|
||||
))}
|
||||
</div>
|
||||
</header>
|
||||
@@ -150,8 +145,8 @@ export const ProvisionerDaemonsPage: FC = () => {
|
||||
>
|
||||
{warnings.length > 0 ? (
|
||||
<div css={{ display: "flex", flexDirection: "column" }}>
|
||||
{warnings.map((warning, i) => (
|
||||
<span key={i}>{warning.message}</span>
|
||||
{warnings.map((warning) => (
|
||||
<span key={warning.code}>{warning.message}</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
@@ -191,32 +186,30 @@ const parseBool = (s: string): { valid: boolean; value: boolean } => {
|
||||
};
|
||||
|
||||
interface ProvisionerTagProps {
|
||||
k: string;
|
||||
v: string;
|
||||
onDelete?: (key: string) => void;
|
||||
tagName: string;
|
||||
tagValue: string;
|
||||
onDelete?: (tagName: string) => void;
|
||||
}
|
||||
|
||||
export const ProvisionerTag: FC<ProvisionerTagProps> = ({ k, v, onDelete }) => {
|
||||
const { valid, value: boolValue } = parseBool(v);
|
||||
const kv = `${k}: ${v}`;
|
||||
export const ProvisionerTag: FC<ProvisionerTagProps> = ({
|
||||
tagName,
|
||||
tagValue,
|
||||
onDelete,
|
||||
}) => {
|
||||
const { valid, value: boolValue } = parseBool(tagValue);
|
||||
const kv = `${tagName}: ${tagValue}`;
|
||||
const content = onDelete ? (
|
||||
<>
|
||||
{kv}
|
||||
<IconButton
|
||||
aria-label={`delete-${k}`}
|
||||
aria-label={`delete-${tagName}`}
|
||||
size="small"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
onDelete(k);
|
||||
onDelete(tagName);
|
||||
}}
|
||||
>
|
||||
<CloseIcon
|
||||
fontSize="inherit"
|
||||
css={{
|
||||
width: 14,
|
||||
height: 14,
|
||||
}}
|
||||
/>
|
||||
<CloseIcon fontSize="inherit" css={{ width: 14, height: 14 }} />
|
||||
</IconButton>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -104,17 +104,19 @@ export const ProvisionerTagsPopover: FC<ProvisionerTagsPopoverProps> = ({
|
||||
}
|
||||
/>
|
||||
<Stack direction="row" spacing={1} wrap="wrap">
|
||||
{Object.keys(tags)
|
||||
.filter((key) => {
|
||||
// filter out owner since you cannot override it
|
||||
return key !== "owner";
|
||||
})
|
||||
.map((k) => (
|
||||
<Fragment key={k}>
|
||||
{k === "scope" ? (
|
||||
<ProvisionerTag k={k} v={tags[k]} />
|
||||
{Object.entries(tags)
|
||||
// filter out owner since you cannot override it
|
||||
.filter(([key]) => key !== "owner")
|
||||
.map(([key, value]) => (
|
||||
<Fragment key={key}>
|
||||
{key === "scope" ? (
|
||||
<ProvisionerTag tagName={key} tagValue={value} />
|
||||
) : (
|
||||
<ProvisionerTag k={k} v={tags[k]} onDelete={onDelete} />
|
||||
<ProvisionerTag
|
||||
tagName={key}
|
||||
tagValue={value}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
@@ -96,9 +96,8 @@ export const traverse = (
|
||||
) => void,
|
||||
parent?: string,
|
||||
) => {
|
||||
for (const filename of Object.keys(fileTree)) {
|
||||
for (const [filename, content] of Object.entries(fileTree)) {
|
||||
const fullPath = parent ? `${parent}/${filename}` : filename;
|
||||
const content = fileTree[filename];
|
||||
callback(content, filename, fullPath);
|
||||
if (typeof content === "object") {
|
||||
traverse(content, callback, fullPath);
|
||||
|
||||
Reference in New Issue
Block a user