Skip to content

Commit

Permalink
Merge pull request #1000 from jetstreamapp/feat/949-copy-query-result…
Browse files Browse the repository at this point in the history
…s-as-csv

Allow copying records to CSV #949
  • Loading branch information
paustint authored Aug 10, 2024
2 parents 22a2a2b + 8e18431 commit 436d86f
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ANALYTICS_KEYS } from '@jetstream/shared/constants';
import { copyRecordsToClipboard } from '@jetstream/shared/ui-utils';
import { Maybe, SalesforceRecord } from '@jetstream/types';
import { CopyAsDataType, Maybe, SalesforceRecord } from '@jetstream/types';
import { ButtonGroupContainer, DropDown, Icon, Modal, Radio, RadioGroup, Tooltip } from '@jetstream/ui';
import { useAmplitude } from '@jetstream/ui-core';
import classNames from 'classnames';
Expand Down Expand Up @@ -29,7 +29,7 @@ export const QueryResultsCopyToClipboard: FunctionComponent<QueryResultsCopyToCl
}) => {
const { trackEvent } = useAmplitude();
const [isModalOpen, setIsModalOpen] = useState(false);
const [format, setFormat] = useState<'excel' | 'json'>('excel');
const [format, setFormat] = useState<CopyAsDataType>('excel');
const [whichRecords, setWhichRecords] = useState<WhichRecords>('all');
const [hasFilteredRows, setHasFilteredRows] = useState<boolean>(false);
const [hasPartialSelectedRows, setHasPartialSelectedRows] = useState<boolean>(false);
Expand All @@ -50,7 +50,7 @@ export const QueryResultsCopyToClipboard: FunctionComponent<QueryResultsCopyToCl
setWhichRecords('all');
}, [records, filteredRows, selectedRows]);

async function handleCopyToClipboard(format: 'excel' | 'json' = 'excel') {
async function handleCopyToClipboard(format: CopyAsDataType = 'excel') {
if (
(records && hasFilteredRows && filteredRows.length < records.length) ||
(records && hasPartialSelectedRows && selectedRows.length < records.length)
Expand Down Expand Up @@ -108,8 +108,11 @@ export const QueryResultsCopyToClipboard: FunctionComponent<QueryResultsCopyToCl
dropDownClassName="slds-dropdown_actions"
position="right"
disabled={!hasRecords}
items={[{ id: 'json', value: 'Copy as JSON' }]}
onSelected={(item) => handleCopyToClipboard(item as 'excel' | 'json')}
items={[
{ id: 'csv', value: 'Copy as CSV' },
{ id: 'json', value: 'Copy as JSON' },
]}
onSelected={(item) => handleCopyToClipboard(item as CopyAsDataType)}
/>
</ButtonGroupContainer>
{isModalOpen && (
Expand Down
14 changes: 9 additions & 5 deletions libs/shared/ui-core/src/record/ViewEditCloneRecord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
BulkDownloadJob,
ChildRelationship,
CloneEditView,
CopyAsDataType,
ErrorResult,
Field,
FileExtCsvXLSXJsonGSheet,
Expand Down Expand Up @@ -371,8 +372,8 @@ export const ViewEditCloneRecord: FunctionComponent<ViewEditCloneRecordProps> =
subqueryFields: Object.keys(record)
.filter((field) => field !== 'attributes')
.reduce((acc, field) => {
if (record[field] && isObject(record[field]) && Array.isArray(record[field]?.records)) {
const firstRecord = record[field]?.records?.[0] || {};
if (record[field] && isObject(record[field]) && Array.isArray((record[field] as any)?.records)) {
const firstRecord = (record[field] as any)?.records?.[0] || {};
acc[field] = Object.keys(firstRecord)
.filter((field) => field !== 'attributes')
.map((field) => {
Expand Down Expand Up @@ -436,7 +437,7 @@ export const ViewEditCloneRecord: FunctionComponent<ViewEditCloneRecordProps> =
});
}

function handleCopyToClipboard(format: 'excel' | 'json' = 'excel') {
function handleCopyToClipboard(format: CopyAsDataType = 'excel') {
if (!initialRecord) {
return;
}
Expand Down Expand Up @@ -602,8 +603,11 @@ export const ViewEditCloneRecord: FunctionComponent<ViewEditCloneRecordProps> =
className="slds-button_last"
dropDownClassName="slds-dropdown_actions"
position="right"
items={[{ id: 'json', value: 'Copy as JSON' }]}
onSelected={(item) => handleCopyToClipboard(item as 'excel' | 'json')}
items={[
{ id: 'csv', value: 'Copy as CSV' },
{ id: 'json', value: 'Copy as JSON' },
]}
onSelected={(item) => handleCopyToClipboard(item as CopyAsDataType)}
/>
</ButtonGroupContainer>
<button
Expand Down
13 changes: 12 additions & 1 deletion libs/shared/ui-utils/src/lib/shared-ui-data-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { REGEX, flattenRecords, groupByFlat, splitArrayToMaxSize } from '@jetstr
import type {
CompositeRequestBody,
CompositeResponse,
CopyAsDataType,
DebugLevel,
DescribeSObjectResult,
DescribeSObjectResultWithExtendedField,
Expand All @@ -19,6 +20,7 @@ import { composeQuery, getField } from '@jetstreamapp/soql-parser-js';
import copyToClipboard from 'copy-to-clipboard';
import { addHours } from 'date-fns/addHours';
import { formatISO } from 'date-fns/formatISO';
import { unparse } from 'papaparse';
import {
isRelationshipField,
polyfillFieldDefinition,
Expand Down Expand Up @@ -301,7 +303,7 @@ export async function fetchActiveLog(org: SalesforceOrgUi, id: string): Promise<
*/
export async function copyRecordsToClipboard(
recordsToCopy: any,
copyFormat: 'excel' | 'json' = 'excel',
copyFormat: CopyAsDataType = 'excel',
fields?: Maybe<string[]>,
includeHeader = true
) {
Expand All @@ -313,6 +315,12 @@ export async function copyRecordsToClipboard(
'text/html': new Blob([transformTabularDataToHtml(recordsToCopy, fields, includeHeader)], { type: 'text/html' }),
});
await navigator.clipboard.write([clipboardItem]);
} else if (copyFormat === 'csv') {
recordsToCopy = fields ? flattenRecords(recordsToCopy, fields) : recordsToCopy;
const clipboardItem = new ClipboardItem({
'text/plain': new Blob([unparse(recordsToCopy, { header: includeHeader })], { type: 'text/plain' }),
});
await navigator.clipboard.write([clipboardItem]);
} else if (copyFormat === 'json') {
const clipboardItem = new ClipboardItem({
'text/plain': new Blob([JSON.stringify(recordsToCopy, null, 2)], { type: 'text/plain' }),
Expand All @@ -325,6 +333,9 @@ export async function copyRecordsToClipboard(
if (copyFormat === 'excel' && fields) {
const flattenedData = flattenRecords(recordsToCopy, fields);
copyToClipboard(transformTabularDataToExcelStr(flattenedData, fields, includeHeader), { format: 'text/plain' });
} else if (copyFormat === 'csv' && fields) {
const flattenedData = flattenRecords(recordsToCopy, fields);
copyToClipboard(unparse(flattenedData, { header: includeHeader }), { format: 'text/plain' });
} else if (copyFormat === 'json') {
copyToClipboard(JSON.stringify(recordsToCopy, null, 2), { format: 'text/plain' });
}
Expand Down
2 changes: 2 additions & 0 deletions libs/types/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { SalesforceOrgEdition } from './salesforce/misc.types';
import { QueryResult } from './salesforce/query.types';
import { InsertUpdateUpsertDeleteQuery } from './salesforce/record.types';

export type CopyAsDataType = 'excel' | 'csv' | 'json';

export interface RequestResult<T> {
data: T;
}
Expand Down

0 comments on commit 436d86f

Please sign in to comment.