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
55 changes: 33 additions & 22 deletions internal/portal/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CreateDestination from "./scenes/CreateDestination/CreateDestination";

type ApiClient = {
fetch: (path: string, init?: RequestInit) => Promise<any>;
fetchRoot: (path: string, init?: RequestInit) => Promise<any>;
};

// API error response from the server
Expand Down Expand Up @@ -96,32 +97,42 @@ function AuthenticatedApp({
tenant: TenantResponse;
token: string;
}) {
const handleResponse = async (res: Response) => {
if (!res.ok) {
let error: ApiError;
try {
const data = await res.json();
error = new ApiError(
data.message || res.statusText,
data.status || res.status,
Array.isArray(data.data) ? data.data : undefined,
);
} catch (e) {
error = new ApiError(res.statusText, res.status);
}
throw error;
}
return res.json();
};

const makeHeaders = (init?: RequestInit) => ({
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
...init?.headers,
});

const apiClient: ApiClient = {
fetch: (path: string, init?: RequestInit) => {
return fetch(`/api/v1/tenants/${tenant.id}/${path}`, {
...init,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
...init?.headers,
},
}).then(async (res) => {
if (!res.ok) {
let error: ApiError;
try {
const data = await res.json();
error = new ApiError(
data.message || res.statusText,
data.status || res.status,
Array.isArray(data.data) ? data.data : undefined,
);
} catch (e) {
error = new ApiError(res.statusText, res.status);
}
throw error;
}
return res.json();
});
headers: makeHeaders(init),
}).then(handleResponse);
},
fetchRoot: (path: string, init?: RequestInit) => {
return fetch(`/api/v1/${path}`, {
...init,
headers: makeHeaders(init),
}).then(handleResponse);
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { showToast } from "../Toast/Toast";
import { ApiContext, formatError } from "../../app";

interface RetryDeliveryButtonProps {
attemptId: string;
eventId: string;
destinationId: string;
disabled: boolean;
loading: boolean;
completed: (success: boolean) => void;
Expand All @@ -14,7 +15,8 @@ interface RetryDeliveryButtonProps {
}

const RetryDeliveryButton: React.FC<RetryDeliveryButtonProps> = ({
attemptId,
eventId,
destinationId,
disabled,
loading,
completed,
Expand All @@ -29,8 +31,12 @@ const RetryDeliveryButton: React.FC<RetryDeliveryButtonProps> = ({
e.stopPropagation();
setRetrying(true);
try {
await apiClient.fetch(`attempts/${attemptId}/retry`, {
await apiClient.fetchRoot("retry", {
method: "POST",
body: JSON.stringify({
event_id: eventId,
destination_id: destinationId,
}),
});
showToast("success", "Retry successful.");
completed(true);
Expand All @@ -41,7 +47,7 @@ const RetryDeliveryButton: React.FC<RetryDeliveryButtonProps> = ({

setRetrying(false);
},
[apiClient, attemptId, completed],
[apiClient, eventId, destinationId, completed],
);

return (
Expand Down
60 changes: 0 additions & 60 deletions internal/portal/src/common/RetryEventButton/RetryEventButton.tsx

This file was deleted.

8 changes: 7 additions & 1 deletion internal/portal/src/destination-types.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { useContext } from "react";
import useSWR from "swr";
import { DestinationTypeReference } from "./typings/Destination";
import { ApiContext } from "./app";

export function useDestinationTypes(): Record<
string,
DestinationTypeReference
> {
const { data } = useSWR<DestinationTypeReference[]>("destination-types");
const apiClient = useContext(ApiContext);
const { data } = useSWR<DestinationTypeReference[]>(
"destination-types",
(path: string) => apiClient.fetchRoot(path),
);
if (!data) {
return {};
}
Expand Down
29 changes: 14 additions & 15 deletions internal/portal/src/scenes/CreateDestination/CreateDestination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import { useNavigate } from "react-router-dom";
import { useContext, useEffect, useState } from "react";
import { ApiContext, formatError } from "../../app";
import { showToast } from "../../common/Toast/Toast";
import useSWR, { mutate } from "swr";
import { mutate } from "swr";
import TopicPicker from "../../common/TopicPicker/TopicPicker";
import { DestinationTypeReference, Filter } from "../../typings/Destination";
import { useDestinationTypes } from "../../destination-types";
import DestinationConfigFields from "../../common/DestinationConfigFields/DestinationConfigFields";
import FilterField from "../../common/FilterField/FilterField";
import { FilterSyntaxGuide } from "../../common/FilterSyntaxGuide/FilterSyntaxGuide";
Expand All @@ -30,7 +31,7 @@ type Step = {
FormFields: (props: {
defaultValue: Record<string, any>;
onChange: (value: Record<string, any>) => void;
destinations?: DestinationTypeReference[];
destinationTypes?: Record<string, DestinationTypeReference>;
}) => React.ReactNode;
action: string;
};
Expand Down Expand Up @@ -96,17 +97,17 @@ const DESTINATION_TYPE_STEP: Step = {
return true;
},
FormFields: ({
destinations,
destinationTypes,
defaultValue,
onChange,
}: {
destinations?: DestinationTypeReference[];
destinationTypes?: Record<string, DestinationTypeReference>;
defaultValue: Record<string, any>;
onChange?: (value: Record<string, any>) => void;
}) => (
<div className="destination-types">
<div className="destination-types__container">
{destinations?.map((destination) => (
{Object.values(destinationTypes ?? {}).map((destination) => (
<label key={destination.type} className="destination-type-option">
<input
type="radio"
Expand Down Expand Up @@ -151,16 +152,14 @@ const CONFIGURATION_STEP: Step = {
},
FormFields: ({
defaultValue,
destinations,
destinationTypes,
onChange,
}: {
defaultValue: Record<string, any>;
destinations?: DestinationTypeReference[];
destinationTypes?: Record<string, DestinationTypeReference>;
onChange?: (value: Record<string, any>) => void;
}) => {
const destinationType = destinations?.find(
(d) => d.type === defaultValue.type,
);
const destinationType = destinationTypes?.[defaultValue.type];
const [filter, setFilter] = useState<Filter>(defaultValue.filter || null);
const [showFilter, setShowFilter] = useState(!!defaultValue.filter);
const [filterValid, setFilterValid] = useState(true);
Expand Down Expand Up @@ -262,8 +261,8 @@ export default function CreateDestination() {
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [stepValues, setStepValues] = useState<Record<string, any>>({});
const [isCreating, setIsCreating] = useState(false);
const { data: destinations } =
useSWR<DestinationTypeReference[]>(`destination-types`);
const destinationTypes = useDestinationTypes();
const hasDestinationTypes = Object.keys(destinationTypes).length > 0;
const [isValid, setIsValid] = useState(false);

const currentStep = steps[currentStepIndex];
Expand All @@ -281,7 +280,7 @@ export default function CreateDestination() {
const createDestination = (values: Record<string, any>) => {
setIsCreating(true);

const destination_type = destinations?.find((d) => d.type === values.type);
const destination_type = destinationTypes[values.type];

let topics: string[];
if (typeof values.topics === "string") {
Expand Down Expand Up @@ -403,10 +402,10 @@ export default function CreateDestination() {
}}
>
<div className="create-destination__step__fields">
{destinations ? (
{hasDestinationTypes ? (
<currentStep.FormFields
defaultValue={stepValues}
destinations={destinations}
destinationTypes={destinationTypes}
onChange={(values) => {
setStepValues((prev) => ({ ...prev, ...values }));
if (currentStep.isValid) {
Expand Down
10 changes: 5 additions & 5 deletions internal/portal/src/scenes/Destination/Events/AttemptDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,17 @@ const AttemptDetails = ({
}: {
navigateAttempt: (path: string, params?: any) => void;
}) => {
const { attempt_id: attemptId } = useParams();
const { attempt_id: attemptId, destination_id: destinationId } = useParams();

const { data: attempt } = useSWR<Attempt>(
`attempts/${attemptId}?include=event.data,response_data`,
`destinations/${destinationId}/attempts/${attemptId}?include=event.data,response_data`,
);

if (!attempt) {
return <div>Loading...</div>;
}

const event =
typeof attempt.event === "object" ? (attempt.event as EventFull) : null;
const event = attempt.event ? (attempt.event as EventFull) : null;

return (
<div className="drawer">
Expand All @@ -33,7 +32,8 @@ const AttemptDetails = ({
</h3>
<div className="drawer__header-actions">
<RetryDeliveryButton
attemptId={attempt.id}
eventId={attempt.event_id}
destinationId={attempt.destination_id}
disabled={false}
loading={false}
completed={() => {}}
Expand Down
11 changes: 4 additions & 7 deletions internal/portal/src/scenes/Destination/Events/Attempts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,9 @@ const Attempts: React.FC<AttemptsProps> = ({
searchParams.set("limit", "15");
}

searchParams.set("destination_id", destination.id);
searchParams.set("include", "event");

return `attempts?${searchParams.toString()}`;
return `destinations/${destination.id}/attempts?${searchParams.toString()}`;
}, [destination.id, timeRange, urlSearchParams]);

const {
Expand All @@ -85,10 +84,7 @@ const Attempts: React.FC<AttemptsProps> = ({

const table_rows = attemptsList?.models
? attemptsList.models.map((attempt) => {
const event =
typeof attempt.event === "object"
? (attempt.event as EventSummary)
: null;
const event = attempt.event ? (attempt.event as EventSummary) : null;
return {
id: attempt.id,
active: attempt.id === (attemptId || ""),
Expand All @@ -109,7 +105,8 @@ const Attempts: React.FC<AttemptsProps> = ({
<Badge text="Failed" danger />
)}
<RetryDeliveryButton
attemptId={attempt.id}
eventId={attempt.event_id}
destinationId={attempt.destination_id}
disabled={isValidating}
loading={isValidating}
completed={(success) => {
Expand Down
8 changes: 5 additions & 3 deletions internal/portal/src/typings/Event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ interface EventFull extends EventSummary {
// Attempt represents a delivery attempt for an event to a destination
interface Attempt {
id: string;
event_id: string;
destination_id: string;
status: "success" | "failed";
delivered_at: string;
code?: string;
response_data?: Record<string, unknown>;
attempt_number: number;
// Expandable fields - string (ID) or object depending on expand param
event: string | EventSummary | EventFull;
destination: string;
manual: boolean;
// Expandable field - only present when using include=event
event?: EventSummary | EventFull;
}

interface SeekPagination {
Expand Down