Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
48 changes: 48 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,48 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/

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

type Props = {
children: ReactNode;
};

type State = {
hasError: boolean;
};

// 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 };

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

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

render() {
if (this.state.hasError) {
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">Something went wrong while loading this view.</p>
<Button variant="secondary" size="sm" onClick={() => this.setState({ hasError: false })}>
Try again
</Button>
</div>
);
}
return this.props.children;
}
}
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
13 changes: 8 additions & 5 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,25 @@ export const PrevExports = observer(function PrevExports(props: Props) {
workspaceSlug && cursor ? () => integrationService.getExportsServicesList(workspaceSlug, cursor, per_page) : null
);

const handleRefresh = () => {
const handleRefresh = useCallback(() => {
setRefreshing(true);
mutate(EXPORT_SERVICES_LIST(workspaceSlug, `${cursor}`, `${per_page}`)).then(() => setRefreshing(false));
};
}, [workspaceSlug, cursor, per_page]);
Comment thread
codingwolf-at marked this conversation as resolved.
Outdated

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 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: any) => 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,7 @@ 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?.map((id) => labelMap[id]).filter(Boolean) || [];
Comment thread
codingwolf-at marked this conversation as resolved.
Outdated

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,7 @@ 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?.map((id) => labelMap[id]).filter(Boolean) || [];
Comment thread
codingwolf-at marked this conversation as resolved.
Outdated

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
2 changes: 1 addition & 1 deletion apps/web/core/components/profile/overview/activity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ 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 ? (
{Array.isArray(userProfileActivity?.results) ? (
userProfileActivity.results.length > 0 ? (
<div className="space-y-5">
{userProfileActivity.results.map((activity) => (
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 = (response.sub_issues ?? []) as TIssue[];
Comment thread
codingwolf-at marked this conversation as resolved.
Outdated

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