Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow deleting reports from query results #1202 #1222

Merged
merged 1 commit into from
Mar 23, 2025
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
7 changes: 4 additions & 3 deletions apps/api/src/app/controllers/sf-misc.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ const salesforceRequest = createRoute(routeDefinition.salesforceRequest.validato
}
});

// TODO: combine with salesforceRequest and rename
// The request payload and response are slightly different, but the logic is the same
// The only difference is the caller is expected to pass in the full url to call (AFAIK)
/**
* Similar to salesforceRequest, but the results are not processed as JSON.
* This is useful for raw text responses, such as when downloading files.
*/
const salesforceRequestManual = createRoute(
routeDefinition.salesforceRequestManual.validators,
async ({ body, jetstreamConn }, req, res, next) => {
Expand Down
2 changes: 1 addition & 1 deletion libs/api-types/src/lib/api-misc.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const SalesforceApiRequestSchema = z.object({
headers: z.record(z.string()).nullish(),
options: z
.object({
responseType: z.enum(['json', 'text']).nullish(),
responseType: z.enum(['json', 'text']).nullish().default('json'),
noContentResponse: z.any().nullish(),
})
.nullish(),
Expand Down
36 changes: 36 additions & 0 deletions libs/shared/data/src/lib/client-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
PullResponse,
PullResponseSchema,
QueryResults,
RecordResult,
RetrieveResult,
SalesforceApiRequest,
SalesforceOrgUi,
Expand Down Expand Up @@ -944,3 +945,38 @@ export async function googleUploadFile(
)
.then((response) => response.data);
}

export async function deleteReportsById(org: SalesforceOrgUi, ids: string[], apiVersion: string): Promise<RecordResult[]> {
const output: RecordResult[] = [];
for (const id of ids) {
try {
const response = await manualRequest(org, {
method: 'DELETE',
url: `/services/data/${apiVersion}/analytics/reports/${id}`,
});

if (response.error) {
const results = JSON.parse(response.body || '[]') as {
errorCode: string;
message: string;
specificErrorCode: number;
}[];
output.push({
errors: results.map((error) => ({ fields: [], message: error.message, statusCode: error.errorCode })),
success: false,
id,
});
continue;
}
output.push({ success: true, id });
} catch (ex) {
output.push({
errors: [{ fields: [], message: 'UNKOWN ERROR', statusCode: 'UNKNOWN' }],
success: false,
id,
});
}
}

return output;
}
25 changes: 19 additions & 6 deletions libs/shared/ui-core/src/jobs/JobWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { MIME_TYPES } from '@jetstream/shared/constants';
import {
bulkApiAddBatchToJob,
bulkApiCreateJob,
deleteReportsById,
queryMore,
retrieveMetadataFromListMetadata,
retrieveMetadataFromManifestFile,
Expand Down Expand Up @@ -71,7 +72,7 @@ export class JobWorker {
}

public async handleMessage(name: AsyncJobType, payloadData: AsyncJobWorkerMessagePayload, port?: MessagePort) {
const { org, job } = payloadData || {};
const { org, job, apiVersion } = payloadData || {};
switch (name) {
case 'CancelJob': {
const { job } = payloadData as AsyncJobWorkerMessagePayload<CancelJob>;
Expand All @@ -87,18 +88,30 @@ export class JobWorker {
let { records, batchSize } = job.meta as { records: SalesforceRecord[]; batchSize?: number };
records = Array.isArray(records) ? records : [records];

batchSize = clamp(batchSize || MAX_DELETE_RECORDS, 1, 200);

const sobject = getSObjectFromRecordUrl(records[0].attributes.url);

const isReport = sobject === 'Report';
const matchBatchSize = isReport ? 25 : 200;

batchSize = clamp(batchSize || MAX_DELETE_RECORDS, 1, matchBatchSize);

const allIds: string[] = records.map((record) => getIdFromRecordUrl(record.attributes.url));

const results: any[] = [];
for (const ids of splitArrayToMaxSize(allIds, batchSize)) {
try {
// TODO: add progress notification and allow cancellation
let tempResults = await sobjectOperation(org, sobject, 'delete', { ids }, { allOrNone: false });
tempResults = ensureArray(tempResults);
tempResults.forEach((result) => results.push(result));

if (isReport) {
// Reports cannot be deleted using the sobjectOperation, so we need to use
// /services/data/v34.0/analytics/reports/00OD0000001cxIE
const tempResults = await deleteReportsById(org, ids, apiVersion);
tempResults.forEach((result) => results.push(result));
} else {
let tempResults = await sobjectOperation(org, sobject, 'delete', { ids }, { allOrNone: false });
tempResults = ensureArray(tempResults);
tempResults.forEach((result) => results.push(result));
}
} catch (ex) {
logger.error('There was a problem deleting these records');
}
Expand Down
4 changes: 3 additions & 1 deletion libs/shared/ui-core/src/jobs/Jobs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const Jobs: FunctionComponent = () => {
const popoverRef = useRef<PopoverRef>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const isOpen = useRef<boolean>(false);
const [{ serverUrl }] = useRecoilState(applicationCookieState);
const [{ serverUrl, defaultApiVersion }] = useRecoilState(applicationCookieState);
const rollbar = useRollbar();
const setJobs = useSetRecoilState(jobsState);
const [jobsUnread, setJobsUnread] = useRecoilState(jobsUnreadState);
Expand Down Expand Up @@ -72,6 +72,7 @@ export const Jobs: FunctionComponent = () => {
data: {
job: { ...job },
org: job.org,
apiVersion: defaultApiVersion,
},
});
});
Expand Down Expand Up @@ -499,6 +500,7 @@ export const Jobs: FunctionComponent = () => {
data: {
job: { ...job, meta: null, results: null },
org: job.org,
apiVersion: defaultApiVersion,
},
});
setJobs((prevJobs) => ({
Expand Down
1 change: 1 addition & 0 deletions libs/types/src/lib/ui/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ export interface AsyncJob<T = unknown, R = unknown> {
export interface AsyncJobWorkerMessagePayload<T = unknown> {
job: AsyncJob<T>;
org: SalesforceOrgUi;
apiVersion: string;
}

export interface AsyncJobWorkerMessageResponse<T = unknown, R = unknown> {
Expand Down