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 {
|
||||
queryKey: ["organizations", organizationIds.sort(), "permissions"],
|
||||
queryKey: ["organizations", [...organizationIds.sort()], "permissions"],
|
||||
queryFn: async () => {
|
||||
// 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
|
||||
@@ -316,7 +316,7 @@ export const workspacePermissionsByOrganization = (
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ["workspaces", organizationIds.sort(), "permissions"],
|
||||
queryKey: ["workspaces", [...organizationIds.sort()], "permissions"],
|
||||
queryFn: async () => {
|
||||
const prefixedChecks = organizationIds.flatMap((orgId) =>
|
||||
Object.entries(workspacePermissionChecks(orgId, userId)).map(
|
||||
|
||||
@@ -4,7 +4,7 @@ export function sortProxiesByLatency(
|
||||
proxies: Proxies,
|
||||
latencies: ProxyLatencies,
|
||||
) {
|
||||
return proxies.toSorted((a, b) => {
|
||||
return [...proxies].sort((a, b) => {
|
||||
const latencyA = latencies?.[a.id]?.latencyMS ?? Number.POSITIVE_INFINITY;
|
||||
const latencyB = latencies?.[b.id]?.latencyMS ?? Number.POSITIVE_INFINITY;
|
||||
return latencyA - latencyB;
|
||||
|
||||
@@ -13,9 +13,9 @@ export const mergeTimeRanges = (ranges: TimeRange[]): TimeRange => {
|
||||
.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
|
||||
const start = sortedDurations[0].startedAt;
|
||||
|
||||
const sortedEndDurations = ranges
|
||||
.slice()
|
||||
.sort((a, b) => a.endedAt.getTime() - b.endedAt.getTime());
|
||||
const sortedEndDurations = [...ranges].sort(
|
||||
(a, b) => a.endedAt.getTime() - b.endedAt.getTime(),
|
||||
);
|
||||
const end = sortedEndDurations[sortedEndDurations.length - 1].endedAt;
|
||||
return { startedAt: start, endedAt: end };
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ const sortVisibleTemplates = (templates: TemplateExample[]) => {
|
||||
// The docker template should be the first template in the list,
|
||||
// as it's the easiest way to get started with Coder.
|
||||
const dockerTemplateId = "docker";
|
||||
return templates.sort((a, b) => {
|
||||
return [...templates].sort((a, b) => {
|
||||
if (a.id === dockerTemplateId) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ const LicensesSettingsPageView: FC<Props> = ({
|
||||
|
||||
{!isLoading && licenses && licenses?.length > 0 && (
|
||||
<Stack spacing={4} className="licenses">
|
||||
{licenses
|
||||
{[...(licenses ?? [])]
|
||||
?.sort(
|
||||
(a, b) =>
|
||||
new Date(b.claims.license_expires).valueOf() -
|
||||
|
||||
@@ -91,7 +91,7 @@ export const DERPPage: FC = () => {
|
||||
<section>
|
||||
<SectionLabel>Regions</SectionLabel>
|
||||
<div css={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
|
||||
{Object.values(regions!)
|
||||
{Object.values(regions ?? {})
|
||||
.filter((region) => {
|
||||
// Values can technically be null
|
||||
return region !== null;
|
||||
|
||||
@@ -170,8 +170,8 @@ const RoleTable: FC<RoleTableProps> = ({
|
||||
</Cond>
|
||||
|
||||
<Cond>
|
||||
{roles
|
||||
?.sort((a, b) => a.name.localeCompare(b.name))
|
||||
{[...(roles ?? [])]
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((role) => (
|
||||
<RoleRow
|
||||
key={role.name}
|
||||
|
||||
@@ -412,7 +412,9 @@ const TemplateUsagePanel: FC<TemplateUsagePanelProps> = ({
|
||||
...panelProps
|
||||
}) => {
|
||||
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 =
|
||||
validUsage?.reduce((total, usage) => total + usage.seconds, 0) ?? 1;
|
||||
const usageColors = chroma
|
||||
@@ -438,86 +440,82 @@ const TemplateUsagePanel: FC<TemplateUsagePanelProps> = ({
|
||||
gap: 24,
|
||||
}}
|
||||
>
|
||||
{validUsage
|
||||
.sort((a, b) => b.seconds - a.seconds)
|
||||
.map((usage, i) => {
|
||||
const percentage = (usage.seconds / totalInSeconds) * 100;
|
||||
return (
|
||||
<div
|
||||
key={usage.slug}
|
||||
css={{ display: "flex", gap: 24, alignItems: "center" }}
|
||||
>
|
||||
{validUsage.map((usage, i) => {
|
||||
const percentage = (usage.seconds / totalInSeconds) * 100;
|
||||
return (
|
||||
<div
|
||||
key={usage.slug}
|
||||
css={{ display: "flex", gap: 24, alignItems: "center" }}
|
||||
>
|
||||
<div css={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||||
<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={{
|
||||
fontSize: 13,
|
||||
color: theme.palette.text.secondary,
|
||||
width: 120,
|
||||
flexShrink: 0,
|
||||
lineHeight: "1.5",
|
||||
width: 20,
|
||||
height: 20,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
<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={{
|
||||
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>
|
||||
)}
|
||||
</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(
|
||||
(a, b) =>
|
||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
|
||||
|
||||
Reference in New Issue
Block a user