Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/core/components/common/activity/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const User = observer(function User(props: TUser) {

return (
<>
{customUserName || actorDetail?.display_name.includes("-intake") ? (
{customUserName || actorDetail?.display_name?.includes("-intake") ? (
<span className="font-medium text-primary">{customUserName || "Plane"}</span>
) : (
<Link
Expand Down
60 changes: 60 additions & 0 deletions apps/web/core/components/common/layout-error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/

import { Component, Fragment } from "react";
import type { ErrorInfo, ReactNode } from "react";
import { AlertTriangle } from "lucide-react";
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";

type Props = {
children: ReactNode;
};

type State = {
hasError: boolean;
retryKey: number;
};

function LayoutErrorFallback({ onRetry }: { onRetry: () => void }) {
const { t } = useTranslation();

return (
<div className="flex h-full w-full flex-col items-center justify-center gap-3 text-center">
<AlertTriangle className="size-8 text-tertiary" />
<p className="text-14 text-secondary">{t("something_went_wrong")}</p>
<Button variant="secondary" size="sm" onClick={onRetry}>
{t("common.retry")}
</Button>
</div>
);
}

// Catches render crashes from a single issue layout (list/kanban/spreadsheet/calendar/gantt)
// so a bad group/column shape degrades to a local fallback instead of taking down the whole page.
export class LayoutErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, retryKey: 0 };

static getDerivedStateFromError(): Partial<State> {
return { hasError: true };
}

componentDidCatch(error: Error, info: ErrorInfo) {
// eslint-disable-next-line no-console
console.error("Issue layout crashed", error, info);
}

handleRetry = () => {
this.setState((prev) => ({ hasError: false, retryKey: prev.retryKey + 1 }));
};

render() {
if (this.state.hasError) {
return <LayoutErrorFallback onRetry={this.handleRetry} />;
}
return <Fragment key={this.state.retryKey}>{this.props.children}</Fragment>;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const DescriptionVersionsRoot = observer(function DescriptionVersionsRoot
entityId && activeVersionId ? `DESCRIPTION_VERSION_DETAILS_${activeVersionId}` : null,
entityId && activeVersionId ? () => fetchHandlers.retrieveDescriptionVersion(entityId, activeVersionId) : null
);
const versions = versionsListResponse?.results;
const versions = Array.isArray(versionsListResponse?.results) ? versionsListResponse.results : undefined;
const versionsCount = versions?.length ?? 0;
const activeVersionDetails = versions?.find((version) => version.id === activeVersionId);
const activeVersionIndex = versions?.findIndex((version) => version.id === activeVersionId);
Expand Down
74 changes: 41 additions & 33 deletions apps/web/core/components/exporter/prev-exports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See the LICENSE file for details.
*/

import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { observer } from "mobx-react";
import useSWR, { mutate } from "swr";
import { MoveLeft, MoveRight, RefreshCw } from "lucide-react";
Expand Down Expand Up @@ -46,22 +46,32 @@ export const PrevExports = observer(function PrevExports(props: Props) {
workspaceSlug && cursor ? () => integrationService.getExportsServicesList(workspaceSlug, cursor, per_page) : null
);

const handleRefresh = () => {
const handleRefresh = useCallback(async () => {
setRefreshing(true);
mutate(EXPORT_SERVICES_LIST(workspaceSlug, `${cursor}`, `${per_page}`)).then(() => setRefreshing(false));
};
try {
await mutate(EXPORT_SERVICES_LIST(workspaceSlug, `${cursor}`, `${per_page}`));
} catch (error) {
// eslint-disable-next-line no-console
console.error("Failed to refresh export services list", error);
} finally {
setRefreshing(false);
}
}, [workspaceSlug, cursor, per_page]);

useEffect(() => {
const interval = setInterval(() => {
if (exporterServices?.results?.some((service) => service.status === "processing")) {
if (
Array.isArray(exporterServices?.results) &&
exporterServices.results.some((service) => service.status === "processing")
) {
handleRefresh();
} else {
clearInterval(interval);
}
}, 3000);

return () => clearInterval(interval);
}, [exporterServices]);
}, [exporterServices, handleRefresh]);

return (
<div>
Expand All @@ -73,7 +83,7 @@ export const PrevExports = observer(function PrevExports(props: Props) {
{refreshing ? t("refreshing") : t("refresh_status")}
</Button>
</div>
{!!exporterServices?.results?.length && (
{Array.isArray(exporterServices?.results) && exporterServices.results.length > 0 && (
<div className="flex items-center gap-2 text-11">
<Button
variant="secondary"
Expand All @@ -97,35 +107,33 @@ export const PrevExports = observer(function PrevExports(props: Props) {
)}
</div>
<div className="flex flex-col">
{exporterServices && exporterServices?.results ? (
exporterServices?.results?.length > 0 ? (
<div>
<div className="divide-y divide-subtle-1">
<Table
columns={columns}
data={exporterServices?.results ?? []}
keyExtractor={(rowData: RowData) => rowData?.id ?? ""}
tHeadClassName="border-b border-subtle"
thClassName="text-left font-medium divide-x-0 text-placeholder"
tBodyClassName="divide-y-0"
tBodyTrClassName="divide-x-0 p-4 h-[40px] text-secondary"
tHeadTrClassName="divide-x-0"
/>
</div>
</div>
) : (
<div className="flex h-full w-full items-center justify-center">
<EmptyStateCompact
assetKey="export"
title={t("settings_empty_state.exports.title")}
description={t("settings_empty_state.exports.description")}
align="start"
rootClassName="py-20"
{!exporterServices ? (
<ImportExportSettingsLoader />
) : Array.isArray(exporterServices.results) && exporterServices.results.length > 0 ? (
<div>
<div className="divide-y divide-subtle-1">
<Table
columns={columns}
data={exporterServices.results}
keyExtractor={(rowData: RowData) => rowData?.id ?? ""}
tHeadClassName="border-b border-subtle"
thClassName="text-left font-medium divide-x-0 text-placeholder"
tBodyClassName="divide-y-0"
tBodyTrClassName="divide-x-0 p-4 h-[40px] text-secondary"
tHeadTrClassName="divide-x-0"
/>
</div>
)
</div>
) : (
<ImportExportSettingsLoader />
<div className="flex h-full w-full items-center justify-center">
<EmptyStateCompact
assetKey="export"
title={t("settings_empty_state.exports.title")}
description={t("settings_empty_state.exports.description")}
align="start"
rootClassName="py-20"
/>
</div>
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ export const SingleIntegrationCard = observer(function SingleIntegrationCard({ i
const handleRemoveIntegration = async () => {
if (!workspaceSlug || !integration || !workspaceIntegrations) return;

const workspaceIntegrationId = workspaceIntegrations?.find((i) => i.integration === integration.id)?.id;
const workspaceIntegrationId = Array.isArray(workspaceIntegrations)
? workspaceIntegrations.find((i) => i.integration === integration.id)?.id
: undefined;

setDeletingIntegration(true);

Expand Down Expand Up @@ -104,7 +106,9 @@ export const SingleIntegrationCard = observer(function SingleIntegrationCard({ i
});
};

const isInstalled = workspaceIntegrations?.find((i: any) => i.integration_detail.id === integration.id);
const isInstalled = Array.isArray(workspaceIntegrations)
? workspaceIntegrations.find((i: IWorkspaceIntegration) => i.integration_detail.id === integration.id)
: undefined;

return (
<div className="flex items-center justify-between gap-2 border-b border-subtle bg-surface-1 px-4 py-6">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { observer } from "mobx-react";
// plane imports
import { EIssueLayoutTypes } from "@plane/types";
// components
import { LayoutErrorBoundary } from "@/components/common/layout-error-boundary";
import { CalendarLayoutLoader } from "@/components/ui/loader/layouts/calendar-layout-loader";
import { GanttLayoutLoader } from "@/components/ui/loader/layouts/gantt-layout-loader";
import { KanbanLayoutLoader } from "@/components/ui/loader/layouts/kanban-layout-loader";
Expand Down Expand Up @@ -58,5 +59,5 @@ export const IssueLayoutHOC = observer(function IssueLayoutHOC(props: Props) {
return <IssueLayoutEmptyState storeType={storeType} />;
}

return <>{props.children}</>;
return <LayoutErrorBoundary key={layout}>{props.children}</LayoutErrorBoundary>;
});
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,11 @@ export const IssueProperties = observer(function IssueProperties(props: IIssuePr
issue.start_date && issue.target_date && displayProperties.start_date && displayProperties.due_date
);

const defaultLabelOptions = issue?.label_ids?.map((id) => labelMap[id]) || [];
const defaultLabelOptions =
issue?.label_ids?.flatMap((id) => {
const label = labelMap[id];
return label ? [label] : [];
}) || [];

const minDate = getDate(issue.start_date);
const maxDate = getDate(issue.target_date);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ export const SpreadsheetLabelColumn = observer(function SpreadsheetLabelColumn(p
// hooks
const { labelMap } = useLabel();

const defaultLabelOptions = issue?.label_ids?.map((id) => labelMap[id]) || [];
const defaultLabelOptions =
issue?.label_ids?.flatMap((id) => {
const label = labelMap[id];
return label ? [label] : [];
}) || [];

return (
<div className="h-11 w-full border-b-[0.5px] border-subtle">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ export const PeekOverviewProperties = observer(function PeekOverviewProperties(p
>
<ButtonAvatars
showTooltip
userIds={createdByDetails?.display_name.includes("-intake") ? null : createdByDetails?.id}
userIds={createdByDetails?.display_name?.includes("-intake") ? null : createdByDetails?.id}
/>
<span className="grow truncate text-body-xs-medium leading-5 text-secondary">
{createdByDetails?.display_name.includes("-intake") ? "Plane" : createdByDetails?.display_name}
{createdByDetails?.display_name?.includes("-intake") ? "Plane" : createdByDetails?.display_name}
</span>
</SidebarPropertyListItem>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ export const DraftIssueProperties = observer(function DraftIssueProperties(props

if (!issue.project_id) return null;

const defaultLabelOptions = issue?.label_ids?.map((id) => labelMap[id]) || [];
const defaultLabelOptions =
issue?.label_ids?.flatMap((id) => {
const label = labelMap[id];
return label ? [label] : [];
}) || [];

const minDate = getDate(issue.start_date);
minDate?.setDate(minDate.getDate());
Expand Down
66 changes: 31 additions & 35 deletions apps/web/core/components/profile/overview/activity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,48 +45,44 @@ export const ProfileActivity = observer(function ProfileActivity() {
<div className="space-y-2">
<h3 className="text-16 font-medium">{t("profile.stats.recent_activity.title")}</h3>
<Card>
{userProfileActivity ? (
userProfileActivity.results.length > 0 ? (
<div className="space-y-5">
{userProfileActivity.results.map((activity) => (
<div key={activity.id} className="flex gap-3">
<Avatar
name={activity.actor_detail?.display_name}
src={getFileURL(activity.actor_detail?.avatar_url)}
size="base"
shape="square"
/>
<div className="-mt-1 w-4/5 break-words">
<p className="inline text-13 text-secondary">
<span className="font-medium text-primary">
{currentUser?.id === activity.actor_detail?.id
? "You"
: activity.actor_detail?.display_name}{" "}
</span>
{activity.field ? (
<ActivityMessage activity={activity} showIssue />
) : (
<span>
created <IssueLink activity={activity} />
</span>
)}
</p>
<p className="text-11 whitespace-nowrap text-secondary">{calculateTimeAgo(activity.created_at)}</p>
</div>
</div>
))}
</div>
) : (
<EmptyStateCompact title={t("no_data_yet")} assetKey="unknown" assetClassName="size-20" />
)
) : (
{!userProfileActivity ? (
<Loader className="space-y-5">
<Loader.Item height="40px" />
<Loader.Item height="40px" />
<Loader.Item height="40px" />
<Loader.Item height="40px" />
<Loader.Item height="40px" />
</Loader>
) : Array.isArray(userProfileActivity.results) && userProfileActivity.results.length > 0 ? (
<div className="space-y-5">
{userProfileActivity.results.map((activity) => (
<div key={activity.id} className="flex gap-3">
<Avatar
name={activity.actor_detail?.display_name}
src={getFileURL(activity.actor_detail?.avatar_url)}
size="base"
shape="square"
/>
<div className="-mt-1 w-4/5 break-words">
<p className="inline text-13 text-secondary">
<span className="font-medium text-primary">
{currentUser?.id === activity.actor_detail?.id ? "You" : activity.actor_detail?.display_name}{" "}
</span>
{activity.field ? (
<ActivityMessage activity={activity} showIssue />
) : (
<span>
created <IssueLink activity={activity} />
</span>
)}
</p>
<p className="text-11 whitespace-nowrap text-secondary">{calculateTimeAgo(activity.created_at)}</p>
</div>
</div>
))}
</div>
) : (
<EmptyStateCompact title={t("no_data_yet")} assetKey="unknown" assetClassName="size-20" />
)}
</Card>
</div>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/core/store/issue/issue-details/sub_issues.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
sub_issue_ids: issueIds,
});

const subIssuesStateDistribution = response?.state_distribution;
const subIssues = response.sub_issues as TIssue[];
const subIssuesStateDistribution = response?.state_distribution ?? {};
const subIssues = Array.isArray(response?.sub_issues) ? response.sub_issues : [];

// fetch other issues states and members when sub-issues are from different project
if (subIssues && subIssues.length > 0) {
Expand Down
Loading