Skip to content
Open
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
1 change: 1 addition & 0 deletions .env.local
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/river_test
OTEL_ENABLED=false
PORT=8080
VITE_RIVER_API_BASE_URL=http://localhost:8080/api
FEATURE_JOB_DELETION_DISABLED=false
12 changes: 10 additions & 2 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,16 @@ func (e *endpoints[TTx]) MountEndpoints(archetype *baseservice.Archetype, logger
DB: executor,
Driver: driver,
Extensions: e.Extensions,
JobDeletionEnabled: e.bundleOpts.JobDeletionEnabled,
JobListHideArgsByDefault: e.bundleOpts.JobListHideArgsByDefault,
Logger: logger,
}

return []apiendpoint.EndpointInterface{
endpoints := []apiendpoint.EndpointInterface{
apiendpoint.Mount(mux, newAutocompleteListEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newFeaturesGetEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newHealthCheckGetEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newJobCancelEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newJobDeleteEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newJobGetEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newJobListEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newJobRetryEndpoint(bundle), mountOpts),
Expand All @@ -117,13 +117,20 @@ func (e *endpoints[TTx]) MountEndpoints(archetype *baseservice.Archetype, logger
apiendpoint.Mount(mux, newQueueUpdateEndpoint(bundle), mountOpts),
apiendpoint.Mount(mux, newStateAndCountGetEndpoint(bundle), mountOpts),
}

if e.bundleOpts.JobDeletionEnabled {
endpoints = append(endpoints, apiendpoint.Mount(mux, newJobDeleteEndpoint(bundle), mountOpts))
}

return endpoints
}

// HandlerOpts are the options for creating a new Handler.
type HandlerOpts struct {
// DevMode is whether the server is running in development mode.
DevMode bool
Endpoints uiendpoints.Bundle
JobDeletionEnabled bool
JobListHideArgsByDefault bool
// LiveFS is whether to use the live filesystem for the frontend.
LiveFS bool
Expand Down Expand Up @@ -186,6 +193,7 @@ func NewHandler(opts *HandlerOpts) (*Handler, error) {
}

opts.Endpoints.Configure(&uiendpoints.BundleOpts{
JobDeletionEnabled: opts.JobDeletionEnabled,
JobListHideArgsByDefault: opts.JobListHideArgsByDefault,
})

Expand Down
10 changes: 6 additions & 4 deletions handler_api_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,9 @@ func (*featuresGetEndpoint[TTx]) Meta() *apiendpoint.EndpointMeta {
type featuresGetRequest struct{}

type featuresGetResponse struct {
Extensions map[string]bool `json:"extensions"`
JobListHideArgsByDefault bool `json:"job_list_hide_args_by_default"`
FeatureJobDeletionEnabled bool `json:"feature_job_deletion_enabled"`
Extensions map[string]bool `json:"extensions"`
JobListHideArgsByDefault bool `json:"job_list_hide_args_by_default"`
}

func (a *featuresGetEndpoint[TTx]) Execute(ctx context.Context, _ *featuresGetRequest) (*featuresGetResponse, error) {
Expand All @@ -186,8 +187,9 @@ func (a *featuresGetEndpoint[TTx]) Execute(ctx context.Context, _ *featuresGetRe
}

return &featuresGetResponse{
Extensions: extensions,
JobListHideArgsByDefault: a.JobListHideArgsByDefault,
FeatureJobDeletionEnabled: a.JobDeletionEnabled,
Extensions: extensions,
JobListHideArgsByDefault: a.JobListHideArgsByDefault,
}, nil
}

Expand Down
11 changes: 6 additions & 5 deletions handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ func TestNewHandlerIntegration(t *testing.T) {

logger := riversharedtest.Logger(t)
server, err := NewHandler(&HandlerOpts{
DevMode: true,
Endpoints: bundle,
LiveFS: true,
Logger: logger,
projectRoot: "./",
DevMode: true,
Endpoints: bundle,
JobDeletionEnabled: true,
LiveFS: true,
Logger: logger,
projectRoot: "./",
})
require.NoError(t, err)
return server
Expand Down
1 change: 1 addition & 0 deletions internal/apibundle/api_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type APIBundle[TTx any] struct {
DB riverdriver.Executor
Driver riverdriver.Driver[TTx]
Extensions func(ctx context.Context) (map[string]bool, error)
JobDeletionEnabled bool
JobListHideArgsByDefault bool
Logger *slog.Logger
}
Expand Down
2 changes: 2 additions & 0 deletions internal/riveruicmd/riveruicmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func initServer[TClient any](ctx context.Context, opts *initServerOpts, createCl
corsOrigins = strings.Split(os.Getenv("CORS_ORIGINS"), ",")
databaseURL = os.Getenv("DATABASE_URL")
devMode = envBooleanTrue(os.Getenv("DEV"))
jobDeletionEnabled = !envBooleanTrue(os.Getenv("FEATURE_JOB_DELETION_DISABLED"))
jobListHideArgsByDefault = envBooleanTrue(os.Getenv("RIVER_JOB_LIST_HIDE_ARGS_BY_DEFAULT"))
host = os.Getenv("RIVER_HOST") // may be left empty to bind to all local interfaces
liveFS = envBooleanTrue(os.Getenv("LIVE_FS"))
Expand Down Expand Up @@ -192,6 +193,7 @@ func initServer[TClient any](ctx context.Context, opts *initServerOpts, createCl
uiHandler, err := riverui.NewHandler(&riverui.HandlerOpts{
DevMode: devMode,
Endpoints: createBundle(client),
JobDeletionEnabled: jobDeletionEnabled,
JobListHideArgsByDefault: jobListHideArgsByDefault,
LiveFS: liveFS,
Logger: opts.logger,
Expand Down
1 change: 1 addition & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const router = createRouter({
basepath: getBasePath(),
context: {
features: {
featureJobDeletionEnabled: true,
hasClientTable: false,
hasProducerTable: false,
producerQueries: false,
Expand Down
6 changes: 6 additions & 0 deletions src/components/JobDetail.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { jobFactory } from "@test/factories/job";
import JobDetail from "./JobDetail";

const meta: Meta<typeof JobDetail> = {
args: {
cancel: () => {},
deleteFn: () => {},
jobDeletionEnabled: true,
retry: () => {},
},
component: JobDetail,
title: "Pages/JobDetail",
};
Expand Down
2 changes: 2 additions & 0 deletions src/components/JobDetail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ test("requires confirmation before deleting a job", async () => {
<JobDetail
cancel={vi.fn()}
deleteFn={deleteFn}
jobDeletionEnabled={true}
job={job}
retry={vi.fn()}
/>,
Expand Down Expand Up @@ -53,6 +54,7 @@ test("cancels job delete confirmation", async () => {
<JobDetail
cancel={vi.fn()}
deleteFn={deleteFn}
jobDeletionEnabled={true}
job={job}
retry={vi.fn()}
/>,
Expand Down
55 changes: 34 additions & 21 deletions src/components/JobDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ import { FormEvent, useState } from "react";
type JobDetailProps = {
cancel: () => void;
deleteFn: () => void;
jobDeletionEnabled: boolean;
job: Job;
retry: () => void;
};

export default function JobDetail({
cancel,
deleteFn,
jobDeletionEnabled,
job,
retry,
}: JobDetailProps) {
Expand Down Expand Up @@ -59,6 +61,7 @@ export default function JobDetail({
<ActionButtons
cancel={cancel}
deleteFn={deleteFn}
jobDeletionEnabled={jobDeletionEnabled}
job={job}
retry={retry}
/>
Expand Down Expand Up @@ -185,7 +188,13 @@ export default function JobDetail({
);
}

function ActionButtons({ cancel, deleteFn, job, retry }: JobDetailProps) {
function ActionButtons({
cancel,
deleteFn,
jobDeletionEnabled,
job,
retry,
}: JobDetailProps) {
const [deleteConfirmationOpen, setDeleteConfirmationOpen] = useState(false);

// Can only delete jobs that aren't running:
Expand Down Expand Up @@ -237,27 +246,31 @@ function ActionButtons({ cancel, deleteFn, job, retry }: JobDetailProps) {
onClick={cancelJob}
text="Cancel"
/>
<ButtonForGroup
disabled={deleteDisabled}
Icon={TrashIcon}
onClick={deleteJob}
text="Delete"
/>
{jobDeletionEnabled && (
<ButtonForGroup
disabled={deleteDisabled}
Icon={TrashIcon}
onClick={deleteJob}
text="Delete"
/>
)}
</span>
<ConfirmationDialog
confirmText="Delete job"
description={
<>
This permanently deletes job{" "}
<span className="font-mono">{job.id.toString()}</span>. This action
cannot be undone.
</>
}
onClose={() => setDeleteConfirmationOpen(false)}
onConfirm={confirmDelete}
open={deleteConfirmationOpen}
title="Delete job?"
/>
{jobDeletionEnabled && (
<ConfirmationDialog
confirmText="Delete job"
description={
<>
This permanently deletes job{" "}
<span className="font-mono">{job.id.toString()}</span>. This
action cannot be undone.
</>
}
onClose={() => setDeleteConfirmationOpen(false)}
onConfirm={confirmDelete}
open={deleteConfirmationOpen}
title="Delete job?"
/>
)}
</>
);
}
Expand Down
1 change: 1 addition & 0 deletions src/components/JobList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ describe("JobList", () => {
const deleteJobs = vi.fn();
const user = userEvent.setup();
const features = createFeatures({
featureJobDeletionEnabled: true,
jobListHideArgsByDefault: false,
});

Expand Down
54 changes: 32 additions & 22 deletions src/components/JobList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export type JobRowsProps = {
deleteJobs: (jobIDs: bigint[]) => void;
hideArgs: boolean;
initialFilters?: Filter[];
jobDeletionEnabled: boolean;
jobs: JobMinimal[];
onFiltersChange?: (filters: Filter[]) => void;
retryJobs: (jobIDs: bigint[]) => void;
Expand Down Expand Up @@ -212,13 +213,15 @@ function JobListActionButtons({
cancel,
className,
deleteFn,
jobDeletionEnabled,
jobIDs,
retry,
state,
}: {
cancel: (jobIDs: bigint[]) => void;
className?: string;
deleteFn: (jobIDs: bigint[]) => void;
jobDeletionEnabled: boolean;
jobIDs: bigint[];
retry: (jobIDs: bigint[]) => void;
state: JobState;
Expand Down Expand Up @@ -279,29 +282,33 @@ function JobListActionButtons({
onClick={cancelJob}
text="Cancel"
/>
<ButtonForGroup
disabled={deleteDisabled}
Icon={TrashIcon}
onClick={deleteJob}
text="Delete"
/>
{jobDeletionEnabled && (
<ButtonForGroup
disabled={deleteDisabled}
Icon={TrashIcon}
onClick={deleteJob}
text="Delete"
/>
)}
</span>
<ConfirmationDialog
confirmText={selectedJobCount === 1 ? "Delete job" : "Delete jobs"}
description={
selectedJobCount === 1
? "This permanently deletes the selected job. This action cannot be undone."
: `This permanently deletes ${selectedJobCount.toString()} selected jobs. This action cannot be undone.`
}
onClose={() => setDeleteConfirmationOpen(false)}
onConfirm={confirmDelete}
open={deleteConfirmationOpen}
title={
selectedJobCount === 1
? "Delete selected job?"
: "Delete selected jobs?"
}
/>
{jobDeletionEnabled && (
<ConfirmationDialog
confirmText={selectedJobCount === 1 ? "Delete job" : "Delete jobs"}
description={
selectedJobCount === 1
? "This permanently deletes the selected job. This action cannot be undone."
: `This permanently deletes ${selectedJobCount.toString()} selected jobs. This action cannot be undone.`
}
onClose={() => setDeleteConfirmationOpen(false)}
onConfirm={confirmDelete}
open={deleteConfirmationOpen}
title={
selectedJobCount === 1
? "Delete selected job?"
: "Delete selected jobs?"
}
/>
)}
</>
);
}
Expand All @@ -313,6 +320,7 @@ const JobRows = ({
deleteJobs,
hideArgs,
initialFilters,
jobDeletionEnabled,
jobs,
onFiltersChange,
retryJobs,
Expand Down Expand Up @@ -382,6 +390,7 @@ const JobRows = ({
<JobListActionButtons
cancel={cancelJobs}
deleteFn={deleteJobs}
jobDeletionEnabled={jobDeletionEnabled}
jobIDs={selectedJobs}
retry={retryJobs}
state={state}
Expand Down Expand Up @@ -554,6 +563,7 @@ const JobList = (props: JobListProps) => {
deleteJobs={deleteJobs}
hideArgs={hideArgs}
initialFilters={initialFilters}
jobDeletionEnabled={features.featureJobDeletionEnabled}
jobs={jobs}
onFiltersChange={onFiltersChange}
retryJobs={retryJobs}
Expand Down
1 change: 1 addition & 0 deletions src/components/WorkflowDetail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ vi.mock("@components/workflow-diagram/WorkflowDiagram", () => ({

const features: Features = {
durablePeriodicJobs: false,
featureJobDeletionEnabled: false,
hasClientTable: false,
hasProducerTable: true,
hasSequenceTable: false,
Expand Down
3 changes: 2 additions & 1 deletion src/routes/jobs/$jobId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const Route = createFileRoute("/jobs/$jobId")({
function JobComponent() {
const { jobId } = Route.useParams();
const navigate = Route.useNavigate();
const { queryOptions } = Route.useRouteContext();
const { features, queryOptions } = Route.useRouteContext();
const refreshSettings = useRefreshSetting();
const queryOptionsWithRefresh = useMemo(
() => ({ ...queryOptions, refetchInterval: refreshSettings.intervalMs }),
Expand Down Expand Up @@ -122,6 +122,7 @@ function JobComponent() {
<JobDetail
cancel={cancelMutation.mutate}
deleteFn={deleteMutation.mutate}
jobDeletionEnabled={features.featureJobDeletionEnabled}
job={job}
retry={retryMutation.mutate}
/>
Expand Down
Loading