mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site): resolve all Array.prototype.toSorted and Array.prototype.sort bugs (#17307)
Closes https://github.com/coder/coder/issues/16759 ## Changes made - Replaced all instances of `Array.prototype.toSorted` with `Array.prototype.sort` to provide better support for older browsers - Updated all `Array.prototype.sort` calls where necessary to remove risks of mutation render bugs - Refactored some code (moved things around, added comments) to make it more clear that certain `.sort` calls are harmless and don't have any risks
This commit is contained in:
@@ -270,7 +270,7 @@ export const organizationsPermissions = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey: ["organizations", organizationIds.sort(), "permissions"],
|
queryKey: ["organizations", [...organizationIds.sort()], "permissions"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
// Only request what we need for the sidebar, which is one edit permission
|
// Only request what we need for the sidebar, which is one edit permission
|
||||||
// per sub-link (settings, groups, roles, and members pages) that tells us
|
// per sub-link (settings, groups, roles, and members pages) that tells us
|
||||||
@@ -316,7 +316,7 @@ export const workspacePermissionsByOrganization = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey: ["workspaces", organizationIds.sort(), "permissions"],
|
queryKey: ["workspaces", [...organizationIds.sort()], "permissions"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const prefixedChecks = organizationIds.flatMap((orgId) =>
|
const prefixedChecks = organizationIds.flatMap((orgId) =>
|
||||||
Object.entries(workspacePermissionChecks(orgId, userId)).map(
|
Object.entries(workspacePermissionChecks(orgId, userId)).map(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export function sortProxiesByLatency(
|
|||||||
proxies: Proxies,
|
proxies: Proxies,
|
||||||
latencies: ProxyLatencies,
|
latencies: ProxyLatencies,
|
||||||
) {
|
) {
|
||||||
return proxies.toSorted((a, b) => {
|
return [...proxies].sort((a, b) => {
|
||||||
const latencyA = latencies?.[a.id]?.latencyMS ?? Number.POSITIVE_INFINITY;
|
const latencyA = latencies?.[a.id]?.latencyMS ?? Number.POSITIVE_INFINITY;
|
||||||
const latencyB = latencies?.[b.id]?.latencyMS ?? Number.POSITIVE_INFINITY;
|
const latencyB = latencies?.[b.id]?.latencyMS ?? Number.POSITIVE_INFINITY;
|
||||||
return latencyA - latencyB;
|
return latencyA - latencyB;
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export const mergeTimeRanges = (ranges: TimeRange[]): TimeRange => {
|
|||||||
.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
||||||
const start = sortedDurations[0].startedAt;
|
const start = sortedDurations[0].startedAt;
|
||||||
|
|
||||||
const sortedEndDurations = ranges
|
const sortedEndDurations = [...ranges].sort(
|
||||||
.slice()
|
(a, b) => a.endedAt.getTime() - b.endedAt.getTime(),
|
||||||
.sort((a, b) => a.endedAt.getTime() - b.endedAt.getTime());
|
);
|
||||||
const end = sortedEndDurations[sortedEndDurations.length - 1].endedAt;
|
const end = sortedEndDurations[sortedEndDurations.length - 1].endedAt;
|
||||||
return { startedAt: start, endedAt: end };
|
return { startedAt: start, endedAt: end };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const sortVisibleTemplates = (templates: TemplateExample[]) => {
|
|||||||
// The docker template should be the first template in the list,
|
// The docker template should be the first template in the list,
|
||||||
// as it's the easiest way to get started with Coder.
|
// as it's the easiest way to get started with Coder.
|
||||||
const dockerTemplateId = "docker";
|
const dockerTemplateId = "docker";
|
||||||
return templates.sort((a, b) => {
|
return [...templates].sort((a, b) => {
|
||||||
if (a.id === dockerTemplateId) {
|
if (a.id === dockerTemplateId) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -99,7 +99,7 @@ const LicensesSettingsPageView: FC<Props> = ({
|
|||||||
|
|
||||||
{!isLoading && licenses && licenses?.length > 0 && (
|
{!isLoading && licenses && licenses?.length > 0 && (
|
||||||
<Stack spacing={4} className="licenses">
|
<Stack spacing={4} className="licenses">
|
||||||
{licenses
|
{[...(licenses ?? [])]
|
||||||
?.sort(
|
?.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
new Date(b.claims.license_expires).valueOf() -
|
new Date(b.claims.license_expires).valueOf() -
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const DERPPage: FC = () => {
|
|||||||
<section>
|
<section>
|
||||||
<SectionLabel>Regions</SectionLabel>
|
<SectionLabel>Regions</SectionLabel>
|
||||||
<div css={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
|
<div css={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
|
||||||
{Object.values(regions!)
|
{Object.values(regions ?? {})
|
||||||
.filter((region) => {
|
.filter((region) => {
|
||||||
// Values can technically be null
|
// Values can technically be null
|
||||||
return region !== null;
|
return region !== null;
|
||||||
|
|||||||
@@ -170,8 +170,8 @@ const RoleTable: FC<RoleTableProps> = ({
|
|||||||
</Cond>
|
</Cond>
|
||||||
|
|
||||||
<Cond>
|
<Cond>
|
||||||
{roles
|
{[...(roles ?? [])]
|
||||||
?.sort((a, b) => a.name.localeCompare(b.name))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
.map((role) => (
|
.map((role) => (
|
||||||
<RoleRow
|
<RoleRow
|
||||||
key={role.name}
|
key={role.name}
|
||||||
|
|||||||
@@ -412,7 +412,9 @@ const TemplateUsagePanel: FC<TemplateUsagePanelProps> = ({
|
|||||||
...panelProps
|
...panelProps
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const validUsage = data?.filter((u) => u.seconds > 0);
|
const validUsage = data
|
||||||
|
?.filter((u) => u.seconds > 0)
|
||||||
|
.sort((a, b) => b.seconds - a.seconds);
|
||||||
const totalInSeconds =
|
const totalInSeconds =
|
||||||
validUsage?.reduce((total, usage) => total + usage.seconds, 0) ?? 1;
|
validUsage?.reduce((total, usage) => total + usage.seconds, 0) ?? 1;
|
||||||
const usageColors = chroma
|
const usageColors = chroma
|
||||||
@@ -438,86 +440,82 @@ const TemplateUsagePanel: FC<TemplateUsagePanelProps> = ({
|
|||||||
gap: 24,
|
gap: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{validUsage
|
{validUsage.map((usage, i) => {
|
||||||
.sort((a, b) => b.seconds - a.seconds)
|
const percentage = (usage.seconds / totalInSeconds) * 100;
|
||||||
.map((usage, i) => {
|
return (
|
||||||
const percentage = (usage.seconds / totalInSeconds) * 100;
|
<div
|
||||||
return (
|
key={usage.slug}
|
||||||
<div
|
css={{ display: "flex", gap: 24, alignItems: "center" }}
|
||||||
key={usage.slug}
|
>
|
||||||
css={{ display: "flex", gap: 24, alignItems: "center" }}
|
<div css={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
css={{ display: "flex", alignItems: "center", gap: 8 }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
css={{
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={usage.icon}
|
|
||||||
alt=""
|
|
||||||
style={{
|
|
||||||
objectFit: "contain",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div css={{ fontSize: 13, fontWeight: 500, width: 200 }}>
|
|
||||||
{usage.display_name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Tooltip
|
|
||||||
title={`${Math.floor(percentage)}%`}
|
|
||||||
placement="top"
|
|
||||||
arrow
|
|
||||||
>
|
|
||||||
<LinearProgress
|
|
||||||
value={percentage}
|
|
||||||
variant="determinate"
|
|
||||||
css={{
|
|
||||||
width: "100%",
|
|
||||||
height: 8,
|
|
||||||
backgroundColor: theme.palette.divider,
|
|
||||||
"& .MuiLinearProgress-bar": {
|
|
||||||
backgroundColor: usageColors[i],
|
|
||||||
borderRadius: 999,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Stack
|
|
||||||
spacing={0}
|
|
||||||
css={{
|
css={{
|
||||||
fontSize: 13,
|
width: 20,
|
||||||
color: theme.palette.text.secondary,
|
height: 20,
|
||||||
width: 120,
|
display: "flex",
|
||||||
flexShrink: 0,
|
alignItems: "center",
|
||||||
lineHeight: "1.5",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatTime(usage.seconds)}
|
<img
|
||||||
{usage.times_used > 0 && (
|
src={usage.icon}
|
||||||
<span
|
alt=""
|
||||||
css={{
|
style={{
|
||||||
fontSize: 12,
|
objectFit: "contain",
|
||||||
color: theme.palette.text.disabled,
|
width: "100%",
|
||||||
}}
|
height: "100%",
|
||||||
>
|
}}
|
||||||
Opened {usage.times_used.toLocaleString()}{" "}
|
/>
|
||||||
{usage.times_used === 1 ? "time" : "times"}
|
</div>
|
||||||
</span>
|
<div css={{ fontSize: 13, fontWeight: 500, width: 200 }}>
|
||||||
)}
|
{usage.display_name}
|
||||||
</Stack>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
<Tooltip
|
||||||
})}
|
title={`${Math.floor(percentage)}%`}
|
||||||
|
placement="top"
|
||||||
|
arrow
|
||||||
|
>
|
||||||
|
<LinearProgress
|
||||||
|
value={percentage}
|
||||||
|
variant="determinate"
|
||||||
|
css={{
|
||||||
|
width: "100%",
|
||||||
|
height: 8,
|
||||||
|
backgroundColor: theme.palette.divider,
|
||||||
|
"& .MuiLinearProgress-bar": {
|
||||||
|
backgroundColor: usageColors[i],
|
||||||
|
borderRadius: 999,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Stack
|
||||||
|
spacing={0}
|
||||||
|
css={{
|
||||||
|
fontSize: 13,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
width: 120,
|
||||||
|
flexShrink: 0,
|
||||||
|
lineHeight: "1.5",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatTime(usage.seconds)}
|
||||||
|
{usage.times_used > 0 && (
|
||||||
|
<span
|
||||||
|
css={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: theme.palette.text.disabled,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Opened {usage.times_used.toLocaleString()}{" "}
|
||||||
|
{usage.times_used === 1 ? "time" : "times"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</PanelContent>
|
</PanelContent>
|
||||||
|
|||||||
@@ -165,7 +165,8 @@ export const AppStatuses: FC<AppStatusesProps> = ({
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Sort statuses chronologically (newest first)
|
// 2. Sort statuses chronologically (newest first) - mutating the value is
|
||||||
|
// fine since it's not an outside parameter
|
||||||
allStatuses.sort(
|
allStatuses.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
|
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
|
||||||
|
|||||||
Reference in New Issue
Block a user