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
5 changes: 5 additions & 0 deletions .changeset/twenty-bulldogs-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@blinkk/root-cms': patch
---

fix: preserve column headers in data sources (#574)
3 changes: 3 additions & 0 deletions packages/root-cms/core/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export interface DataSource {
export interface DataSourceData<T = any> {
dataSource: DataSource;
data: T;
/** Optional list of column headers (for gsheet sources). */
headers?: string[];
}

export type DataSourceMode = 'draft' | 'published';
Expand Down Expand Up @@ -822,6 +824,7 @@ export class RootCMSClient {
batch.set(dataDocRefPublished, {
dataSource: updatedDataSource,
data: dataRes?.data || null,
...(dataRes?.headers ? {headers: dataRes.headers} : {}),
});
batch.update(dataDocRefDraft, {
dataSource: updatedDataSource,
Expand Down
25 changes: 14 additions & 11 deletions packages/root-cms/ui/pages/DataSourcePage/DataSourcePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ DataSourcePage.DataSection = (props: {
dataSource: DataSource;
data: DataSourceData;
}) => {
const {data} = props.data || {};
const {data, headers: storedHeaders} = props.data || {};
const dataSource = props.dataSource;

if (!data) {
Expand All @@ -146,22 +146,25 @@ DataSourcePage.DataSection = (props: {

const dataFormat = dataSource.dataFormat || 'map';
if (dataSource.type === 'gsheet') {
let headers: string[] | undefined = undefined;
let headers: string[] | undefined = storedHeaders;
let rows: any[] = [];
if (dataFormat === 'array') {
rows = data as string[][];
} else if (dataFormat === 'map') {
// Reformat Array<Record<string, string>> to string[][].
const headersSet = new Set<string>();
const items = data as any[];
items.forEach((item) => {
for (const key in item) {
if (key) {
headersSet.add(key);
if (!headers) {
// Reformat Array<Record<string, string>> to string[][]. Preserve key order
// by iterating through object keys as encountered.
const headersSet = new Set<string>();
items.forEach((item) => {
for (const key in item) {
if (key) {
headersSet.add(key);
}
}
}
});
headers = Array.from(headersSet);
});
headers = Array.from(headersSet);
}
items.forEach((item) => {
rows.push(headers!.map((header) => item[header] || ''));
});
Expand Down
33 changes: 27 additions & 6 deletions packages/root-cms/ui/utils/data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export interface DataSource {
export interface DataSourceData<T = any> {
dataSource: DataSource;
data: T;
/** Optional list of column headers (for gsheet sources). */
headers?: string[];
}

export async function addDataSource(
Expand Down Expand Up @@ -157,7 +159,7 @@ export async function syncDataSource(id: string) {
throw new Error(`sync failed: ${err}`);
}
} else {
const data = await fetchData(dataSource);
const {data, headers} = await fetchData(dataSource);

const projectId = window.__ROOT_CTX.rootConfig.projectId;
const db = window.firebase.db;
Expand All @@ -183,6 +185,7 @@ export async function syncDataSource(id: string) {
batch.set(dataDocRef, {
dataSource: updatedDataSource,
data: data,
...(headers ? {headers} : {}),
});
batch.update(dataSourceDocRef, {
syncedAt: Timestamp.now(),
Expand Down Expand Up @@ -235,6 +238,7 @@ export async function publishDataSource(id: string) {
batch.set(dataDocRefPublished, {
dataSource: updatedDataSource,
data: dataRes?.data || null,
...(dataRes?.headers ? {headers: dataRes.headers} : {}),
});
batch.update(dataDocRefDraft, {
dataSource: updatedDataSource,
Expand Down Expand Up @@ -280,17 +284,23 @@ export async function deleteDataSource(id: string) {
logAction('datasource.delete', {metadata: {datasourceId: id}});
}

async function fetchData(dataSource: DataSource) {
interface FetchedData {
data: any;
headers?: string[];
}

async function fetchData(dataSource: DataSource): Promise<FetchedData> {
if (dataSource.type === 'http') {
return await fetchHttpData(dataSource);
const data = await fetchHttpData(dataSource);
return {data};
}
if (dataSource.type === 'gsheet') {
return await fetchGsheetData(dataSource);
}
throw new Error(`unsupported data source: ${dataSource.type}`);
}

async function fetchGsheetData(dataSource: DataSource) {
async function fetchGsheetData(dataSource: DataSource): Promise<FetchedData> {
const gsheetId = parseSpreadsheetUrl(dataSource.url);
if (!gsheetId?.spreadsheetId) {
throw new Error(`failed to parse google sheet url: ${dataSource.url}`);
Expand All @@ -303,10 +313,21 @@ async function fetchGsheetData(dataSource: DataSource) {
}

const dataFormat = dataSource.dataFormat || 'map';
const [headers, rows] = await gsheet.getValues();
if (dataFormat === 'array') {
return await gsheet.getValues();
return {data: [headers, rows], headers};
}
return await gsheet.getValuesMap();
const mapData = rows.map((row) => {
const item: Record<string, string> = {};
row.forEach((val, i) => {
const key = headers[i];
if (key) {
item[key] = String(val || '');
}
});
return item;
});
return {data: mapData, headers};
}

async function fetchHttpData(dataSource: DataSource) {
Expand Down