Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
9 changes: 6 additions & 3 deletions apps/backend/src/modules/documents/document.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ class DocumentController extends BaseController {
* @swagger
* /documents/{id}/resume:
* post:
* description: Back to processing, and re-enqueue what still needs work
* description: Back to processing, and re-enqueue what still needs work. Returns current document DTO
* security:
* - bearerAuth: []
* parameters:
Expand All @@ -644,9 +644,12 @@ class DocumentController extends BaseController {
private async resume(
options: DocumentIdHandlerOptions,
): Promise<APIHandlerResponse> {
await this.documentService.resume(options.params.id, options.user.userId);
const document = await this.documentService.resume(
options.params.id,
options.user.userId,
);
return {
payload: null,
payload: document,
status: HTTPCode.OK,
};
}
Expand Down
57 changes: 57 additions & 0 deletions apps/backend/src/modules/documents/document.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,63 @@ class DocumentRepository {
return document ? DocumentEntity.initialize(document) : null;
}

public async findByIdAndOwnerIdForUpdateWithDetails(
id: number,
ownerId: number,
trx: Transaction,
): Promise<DocumentDetailsEntity | null> {
const knex = this.documentModel.knex();

const document = await knex
.select<DocumentDetailsRow>([
"dp.documentId as id",
"d.error_message as errorMessage",
"dp.title",
"dp.status",
"dp.pageCount",
"dp.cursorPageNo",
knex.raw("round(dp.budget_usd, 2)::text as ??", ["budgetUsd"]),
knex.raw("round(dp.spent_usd, 2)::text as ??", ["spentUsd"]),
knex.raw(
"coalesce(round(dp.spent_usd / nullif(dp.budget_usd, 0) * 100, 1), 0)::float8 as ??",
["usedPct"],
),
"pr.id as presetId",
"pr.name as presetName",
"pr.version as presetVersion",
"dp.pagesTotal",
"dp.pagesVerified",
"dp.pagesReadyToCheck",
"dp.pagesInWork",
"dp.pagesPending",
"dp.pagesFailed",
"dp.pagesBlank",
"dp.pagesSkipped",
"dp.verifiedPct",
"dp.closedPct",
])
.from(`${DatabaseTableName.DOCUMENT} as d`)
.innerJoin(
`${DatabaseTableName.DOCUMENT_PROGRESS} as dp`,
"dp.documentId",
"d.id",
)
.innerJoin(`${DatabaseTableName.PRESET} as pr`, "pr.id", "d.presetId")
.where({
"d.id": id,
"d.ownerId": ownerId,
})
.transacting(trx)
.forUpdate("d")
.first();

if (!document) {
return null;
}

return DocumentDetailsEntity.initialize(document);
}

public async findDraftsOlderThan(date: string): Promise<DocumentEntity[]> {
const documents = await this.documentModel
.query()
Expand Down
82 changes: 50 additions & 32 deletions apps/backend/src/modules/documents/document.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,13 +460,6 @@ class DocumentService {
});
}

private throwInvalidStatusToResumeError(): never {
throw new HTTPError({
message: DocumentValidationMessage.INVALID_STATUS_TO_RESUME,
status: HTTPCode.CONFLICT,
});
}

public async create({
fileBytes,
fileName,
Expand Down Expand Up @@ -849,39 +842,62 @@ class DocumentService {
this.throwInvalidStatusToPauseError();
}
}
public async resume(documentId: number, userId: number): Promise<void> {
const pages = await DocumentModel.transaction(async (trx) => {
const document =
await this.documentRepository.findByIdAndOwnerIdForUpdate(
public async resume(
documentId: number,
userId: number,
): Promise<DocumentGetByIdResponseDto> {
const { document, isPaused, pages } = await DocumentModel.transaction(
async (trx) => {
const document =
await this.documentRepository.findByIdAndOwnerIdForUpdateWithDetails(
documentId,
userId,
trx,
);

if (!document) {
this.throwDocumentNotFoundError();
}

const documentObject = document.toObject();

if (documentObject.status !== DocumentStatus.PAUSED) {
return { document: documentObject, isPaused: false, pages: [] };
}

await this.documentRepository.updateStatus(
documentId,
userId,
DocumentStatus.PROCESSING,
trx,
);
await this.pageRepository.updateFirstPendingPagesAsQueued(
documentId,
PAGES_TO_QUEUE,
trx,
);

if (!document) {
this.throwDocumentNotFoundError();
}

if (document.toObject().status !== DocumentStatus.PAUSED) {
this.throwInvalidStatusToResumeError();
}
const pages = await this.pageRepository.findQueuedPages(
documentId,
trx,
);

await this.documentRepository.updateStatus(
documentId,
DocumentStatus.PROCESSING,
trx,
);
await this.pageRepository.updateFirstPendingPagesAsQueued(
documentId,
PAGES_TO_QUEUE,
trx,
);
return {
document: {
...documentObject,
status: DocumentStatus.PROCESSING,
},
isPaused: true,
pages,
};
},
);

return await this.pageRepository.findQueuedPages(documentId, trx);
});
if (!isPaused) {
return document;
}

if (pages.length === EMPTY_COLLECTION_LENGTH) {
return;
return document;
}

try {
Expand All @@ -896,6 +912,8 @@ class DocumentService {
});
}),
);

return document;
} catch (error) {
await this.documentRepository.updateOwnedStatusFrom({
currentStatus: DocumentStatus.PROCESSING,
Expand Down
6 changes: 4 additions & 2 deletions apps/frontend/src/modules/documents/documents-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ class DocumentApi extends BaseHTTPApi {
);
}

public async resume(id: number): Promise<void> {
await this.load(
public async resume(id: number): Promise<DocumentGetByIdResponseDto> {
const response = await this.load(
this.getFullEndpoint(DocumentsApiPath.RESUME, {
id: String(id),
}),
Expand All @@ -160,6 +160,8 @@ class DocumentApi extends BaseHTTPApi {
payload: JSON.stringify({}),
},
);

return await response.json<DocumentGetByIdResponseDto>();
}

public async updateBudget(
Expand Down
10 changes: 7 additions & 3 deletions apps/frontend/src/modules/documents/slices/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,18 @@ const pollDocumentById = createAsyncThunk<
{ serializeError },
);

const resume = createAsyncThunk<number, number, AsyncThunkConfig>(
const resume = createAsyncThunk<
DocumentGetByIdResponseDto,
number,
AsyncThunkConfig
>(
`${sliceName}/resume`,
async (id, { extra }) => {
const { documentApi } = extra;

await documentApi.resume(id);
const document = await documentApi.resume(id);

return id;
return document;
},
{ serializeError },
);
Expand Down
25 changes: 18 additions & 7 deletions apps/frontend/src/modules/documents/slices/documents.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,18 @@ const { actions, name, reducer } = createSlice({
});
builder.addCase(pollDocumentById.fulfilled, (state, action) => {
if (state.document && state.document.id === action.payload.id) {
state.document = action.payload;
const isProcessingOrPaused =
action.payload.status === DocumentStatus.PROCESSING ||
action.payload.status === DocumentStatus.PAUSED;

if (
state.document.status === DocumentStatus.PAUSED &&
isProcessingOrPaused
) {
return;
}

state.document = { ...action.payload };
}
});
builder.addCase(remove.fulfilled, (state, action) => {
Expand All @@ -109,16 +120,12 @@ const { actions, name, reducer } = createSlice({
});
builder.addCase(pause.fulfilled, (state, action) => {
state.pauseResumeDataStatuses[action.meta.arg] = DataStatus.FULFILLED;

if (state.document && state.document.id === action.meta.arg) {
state.document.status = DocumentStatus.PAUSED;
}
});
builder.addCase(resume.fulfilled, (state, action) => {
state.pauseResumeDataStatuses[action.meta.arg] = DataStatus.FULFILLED;

if (state.document && state.document.id === action.meta.arg) {
state.document.status = DocumentStatus.PROCESSING;
state.document = action.payload;
}
});
builder.addCase(pause.pending, (state, action) => {
Expand All @@ -138,7 +145,11 @@ const { actions, name, reducer } = createSlice({
builder.addCase(pause.rejected, (state, action) => {
state.pauseResumeDataStatuses[action.meta.arg] = DataStatus.REJECTED;

if (state.document && state.document.id === action.meta.arg) {
if (
state.document &&
state.document.id === action.meta.arg &&
state.document.status === DocumentStatus.PAUSED
) {
state.document.status = DocumentStatus.PROCESSING;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ const DocumentStatusBlock: React.FC<Properties> = ({ documentId, status }) => {
const isPauseResumeLoading = pauseResumeDataStatus === DataStatus.PENDING;

const handleToggleProcessing = useCallback(() => {
const action = isPaused ? documentActions.resume : documentActions.pause;

void dispatch(action(documentId))
.unwrap()
.then(() => {
if (isPaused) {
void dispatch(documentActions.startPolling(documentId));
}
});
const togglePromise = isPaused
? dispatch(documentActions.resume(documentId))
: dispatch(documentActions.pause(documentId));

void togglePromise.unwrap().then(() => {
if (isPaused) {
void dispatch(documentActions.startPolling(documentId));
}
});
}, [dispatch, documentId, isPaused]);

const showProcessingToggle =
Expand Down
Loading