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
34 changes: 23 additions & 11 deletions internal/portal/src/common/JSONViewer/JSONViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ const JSONViewer = ({ data, label }: JSONViewerProps) => {
return paths;
}, [data]);

const [expanded, setExpanded] = useState<Set<string>>(() => new Set(allPaths));
const [expanded, setExpanded] = useState<Set<string>>(
() => new Set(allPaths),
);

const allExpanded = expanded.size >= allPaths.size;

Expand All @@ -52,14 +54,22 @@ const JSONViewer = ({ data, label }: JSONViewerProps) => {
<div className="json-viewer__header">
{label && <h3 className="subtitle-m">{label}</h3>}
<div className="json-viewer__actions">
<button className="json-viewer__expand-all mono-xs" onClick={handleToggle}>
<button
className="json-viewer__expand-all mono-xs"
onClick={handleToggle}
>
{allExpanded ? "Collapse all" : "Expand all"}
</button>
<CopyButton value={JSON.stringify(data, null, 2)} />
</div>
</div>
<div className="json-viewer__content mono-s">
<JSONNode value={data} path="$" expanded={expanded} toggleNode={toggleNode} />
<JSONNode
value={data}
path="$"
expanded={expanded}
toggleNode={toggleNode}
/>
</div>
</div>
);
Expand Down Expand Up @@ -157,7 +167,8 @@ const CollapsibleNode = ({
if (count === 0) {
return (
<span className="json-viewer__bracket">
{openBracket}{closeBracket}
{openBracket}
{closeBracket}
</span>
);
}
Expand All @@ -168,10 +179,10 @@ const CollapsibleNode = ({
if (!isExpanded) {
return (
<button className="json-viewer__summary" onClick={toggle}>
<span className="json-viewer__bracket">{openBracket}</span>
{" "}
<span className="json-viewer__summary-count">{itemLabel} {arrow}</span>
{" "}
<span className="json-viewer__bracket">{openBracket}</span>{" "}
<span className="json-viewer__summary-count">
{itemLabel} {arrow}
</span>{" "}
<span className="json-viewer__bracket">{closeBracket}</span>
</button>
);
Expand All @@ -180,9 +191,10 @@ const CollapsibleNode = ({
return (
<span className="json-viewer__node">
<button className="json-viewer__summary" onClick={toggle}>
<span className="json-viewer__bracket">{openBracket}</span>
{" "}
<span className="json-viewer__summary-count">{itemLabel} {arrow}</span>
<span className="json-viewer__bracket">{openBracket}</span>{" "}
<span className="json-viewer__summary-count">
{itemLabel} {arrow}
</span>
</button>
<div className="json-viewer__entries">
{entries.map((entry, i) => (
Expand Down
5 changes: 1 addition & 4 deletions internal/portal/src/common/MetricsChart/useMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,7 @@ export function useBatchedMetrics({
}
}

params.set(
"granularity",
granularityOverride ?? getGranularity(timeframe),
);
params.set("granularity", granularityOverride ?? getGranularity(timeframe));

return `metrics/attempts?${params.toString()}`;
}, [idsKey, measuresKey, filtersKey, granularityOverride, timeframe]);
Expand Down
6 changes: 5 additions & 1 deletion internal/portal/src/common/Sparkline/Sparkline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ const Sparkline: React.FC<SparklineProps> = ({ data }) => {
const successPct = 100 - failPct;

return (
<div key={i} className="sparkline__bar" style={{ height: `${height}%` }}>
<div
key={i}
className="sparkline__bar"
style={{ height: `${height}%` }}
>
{d.failed > 0 && (
<div
className="sparkline__segment sparkline__segment--error"
Expand Down
11 changes: 5 additions & 6 deletions internal/portal/src/destination-types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ import useSWR from "swr";
import type { DestinationTypeReference } from "./typings/Destination";
import { ApiContext } from "./app";

export function useDestinationTypes(): Record<
string,
DestinationTypeReference
> {
export function useDestinationTypes():
| Record<string, DestinationTypeReference>
| undefined {
const apiClient = useContext(ApiContext);
const { data } = useSWR<DestinationTypeReference[]>(
"destination-types",
(path: string) => apiClient.fetchRoot(path),
{ revalidateIfStale: false },
);
if (!data) {
return {};
return undefined;
}
return data.reduce(
(acc, type) => {
Expand All @@ -30,7 +29,7 @@ export function useDestinationType(
): DestinationTypeReference | undefined {
const destination_types = useDestinationTypes();

if (!type) {
if (!type || !destination_types) {
return undefined;
}

Expand Down
24 changes: 20 additions & 4 deletions internal/portal/src/scenes/CreateDestination/CreateDestination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ import {
useNavigate,
useSearchParams,
} from "react-router-dom";
import { useState, useMemo, useCallback, useEffect, createContext, useContext } from "react";
import {
useState,
useMemo,
useCallback,
useEffect,
createContext,
useContext,
} from "react";
import type { DestinationTypeReference } from "../../typings/Destination";
import { useDestinationTypes } from "../../destination-types";
import CONFIGS from "../../config";
Expand Down Expand Up @@ -73,8 +80,9 @@ export default function CreateDestination() {
const location = useLocation();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const destinationTypes = useDestinationTypes();
const hasDestinationTypes = Object.keys(destinationTypes).length > 0;
const loadedDestinationTypes = useDestinationTypes();
const hasDestinationTypes = loadedDestinationTypes !== undefined;
const destinationTypes = loadedDestinationTypes ?? {};

// Hydrate stepValues from URL search params on mount (supports page refresh)
const [stepValues, setStepValues] = useState<Record<string, any>>(() => {
Expand Down Expand Up @@ -145,7 +153,15 @@ export default function CreateDestination() {
steps,
buildSearchParams,
}),
[stepValues, setStepValues, destinationTypes, hasDestinationTypes, nextPath, steps, buildSearchParams],
[
stepValues,
setStepValues,
destinationTypes,
hasDestinationTypes,
nextPath,
steps,
buildSearchParams,
],
);

return (
Expand Down
12 changes: 2 additions & 10 deletions internal/portal/src/scenes/CreateDestination/steps/ConfigStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import Button from "../../../common/Button/Button";
import DestinationConfigFields from "../../../common/DestinationConfigFields/DestinationConfigFields";
import FilterField from "../../../common/FilterField/FilterField";
import { FilterSyntaxGuide } from "../../../common/FilterSyntaxGuide/FilterSyntaxGuide";
import {
AddIcon,
CloseIcon,
HelpIcon,
Loading,
} from "../../../common/Icons";
import { AddIcon, CloseIcon, HelpIcon, Loading } from "../../../common/Icons";
import { useSidebar } from "../../../common/Sidebar/Sidebar";
import type { Filter } from "../../../typings/Destination";
import { getFormValues } from "../../../utils/formHelper";
Expand Down Expand Up @@ -196,10 +191,7 @@ export default function ConfigStep() {
<Button
type="button"
onClick={() =>
sidebar.toggle(
"filter-syntax",
<FilterSyntaxGuide />,
)
sidebar.toggle("filter-syntax", <FilterSyntaxGuide />)
}
className="filter-section__guide-button"
>
Expand Down
8 changes: 6 additions & 2 deletions internal/portal/src/scenes/Destination/DestinationMetrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ const DestinationMetrics: React.FC<DestinationMetricsProps> = ({
</div>

{/* Row 2 — Error Breakdown (3 columns) */}
<div className={`metrics-container__cell metrics-container__cell--row2${destination.type !== "webhook" ? " metrics-container__cell--row2-half" : ""}`}>
<div
className={`metrics-container__cell metrics-container__cell--row2${destination.type !== "webhook" ? " metrics-container__cell--row2-half" : ""}`}
>
<MetricsChart
title="Errors"
subtitle="rate"
Expand Down Expand Up @@ -214,7 +216,9 @@ const DestinationMetrics: React.FC<DestinationMetricsProps> = ({
/>
</div>
)}
<div className={`metrics-container__cell metrics-container__cell--row2${destination.type !== "webhook" ? " metrics-container__cell--row2-half" : ""}`}>
<div
className={`metrics-container__cell metrics-container__cell--row2${destination.type !== "webhook" ? " metrics-container__cell--row2-half" : ""}`}
>
<MetricsBreakdown
title="Events count by topic"
data={by_topic.data?.data}
Expand Down
129 changes: 66 additions & 63 deletions internal/portal/src/scenes/DestinationsList/DestinationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ const DestinationList: React.FC = () => {
() => destinations?.map((d) => d.id) ?? [],
[destinations],
);
const { data: batchedMetrics, isLoading: metricsLoading } =
useBatchedMetrics({
const { data: batchedMetrics, isLoading: metricsLoading } = useBatchedMetrics(
{
measures: ["successful_count", "failed_count"],
destinationIds,
timeframe: "24h",
granularity: "4h",
filters: {},
});
},
);

const [searchTerm, setSearchTerm] = useState("");
const [selectedStatus, setSelectedStatus] = useState<Record<string, boolean>>(
Expand Down Expand Up @@ -83,67 +84,69 @@ const DestinationList: React.FC = () => {
})
: [];

const table_rows =
filtered_destinations?.map((destination) => ({
id: destination.id,
entries: [
<>
<div
style={{ minWidth: "16px", width: "16px", display: "flex" }}
dangerouslySetInnerHTML={{
__html: destination_types[destination.type].icon as string,
}}
/>
<span className="subtitle-m">
{destination_types[destination.type].label}
</span>
</>,
<span className="muted-variant">{destination.target}</span>,
CONFIGS.TOPICS ? (
<Tooltip
content={
<div className="destination-list__topics-tooltip">
{(destination.topics.length > 0 && destination.topics[0] === "*"
? CONFIGS.TOPICS.split(",")
: destination.topics
)
.slice(0, 9)
.map((topic) => (
<Badge key={topic} text={topic.trim()} />
))}
{(destination.topics[0] === "*"
? CONFIGS.TOPICS.split(",").length
: destination.topics.length) > 9 && (
<span className="subtitle-s muted">
+{" "}
{(destination.topics[0] === "*"
? CONFIGS.TOPICS.split(",").length
: destination.topics.length) - 9}{" "}
more
</span>
)}
</div>
}
>
<span className="muted-variant">
{destination.topics.length > 0 && destination.topics[0] === "*"
? "All"
: destination.topics.length}
const table_rows = destination_types
? filtered_destinations?.map((destination) => ({
id: destination.id,
entries: [
<>
<div
style={{ minWidth: "16px", width: "16px", display: "flex" }}
dangerouslySetInnerHTML={{
__html: destination_types[destination.type].icon as string,
}}
/>
<span className="subtitle-m">
{destination_types[destination.type].label}
</span>
</Tooltip>
) : null,
destination.disabled_at ? (
<Badge text="Disabled" />
) : (
<Badge text="Active" success />
),
<DestinationEventsCell
metricsData={batchedMetrics?.[destination.id]}
isLoading={metricsLoading}
/>,
].filter((entry) => entry !== null),
link: `/destinations/${destination.id}`,
})) || [];
</>,
<span className="muted-variant">{destination.target}</span>,
CONFIGS.TOPICS ? (
<Tooltip
content={
<div className="destination-list__topics-tooltip">
{(destination.topics.length > 0 &&
destination.topics[0] === "*"
? CONFIGS.TOPICS.split(",")
: destination.topics
)
.slice(0, 9)
.map((topic) => (
<Badge key={topic} text={topic.trim()} />
))}
{(destination.topics[0] === "*"
? CONFIGS.TOPICS.split(",").length
: destination.topics.length) > 9 && (
<span className="subtitle-s muted">
+{" "}
{(destination.topics[0] === "*"
? CONFIGS.TOPICS.split(",").length
: destination.topics.length) - 9}{" "}
more
</span>
)}
</div>
}
>
<span className="muted-variant">
{destination.topics.length > 0 && destination.topics[0] === "*"
? "All"
: destination.topics.length}
</span>
</Tooltip>
) : null,
destination.disabled_at ? (
<Badge text="Disabled" />
) : (
<Badge text="Active" success />
),
<DestinationEventsCell
metricsData={batchedMetrics?.[destination.id]}
isLoading={metricsLoading}
/>,
].filter((entry) => entry !== null),
link: `/destinations/${destination.id}`,
})) || []
: [];

const logo = getLogo();

Expand Down
Loading