feat: allow using the existing numeric table IDs in the API (#690)

pull/694/head^2
CamilleLegeron 8 months ago committed by GitHub
parent ad037e700c
commit f66ecbd6df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -85,7 +85,7 @@ import {AccessTokenOptions, AccessTokenResult, GristDocAPI, UIRowId} from 'app/p
import {compileAclFormula} from 'app/server/lib/ACLFormula'; import {compileAclFormula} from 'app/server/lib/ACLFormula';
import {AssistanceSchemaPromptV1Context} from 'app/server/lib/Assistance'; import {AssistanceSchemaPromptV1Context} from 'app/server/lib/Assistance';
import {AssistanceContext} from 'app/common/AssistancePrompts'; import {AssistanceContext} from 'app/common/AssistancePrompts';
import {Authorizer} from 'app/server/lib/Authorizer'; import {Authorizer, RequestWithLogin} from 'app/server/lib/Authorizer';
import {checksumFile} from 'app/server/lib/checksumFile'; import {checksumFile} from 'app/server/lib/checksumFile';
import {Client} from 'app/server/lib/Client'; import {Client} from 'app/server/lib/Client';
import {DEFAULT_CACHE_TTL, DocManager} from 'app/server/lib/DocManager'; import {DEFAULT_CACHE_TTL, DocManager} from 'app/server/lib/DocManager';
@ -141,6 +141,7 @@ import remove = require('lodash/remove');
import sum = require('lodash/sum'); import sum = require('lodash/sum');
import without = require('lodash/without'); import without = require('lodash/without');
import zipObject = require('lodash/zipObject'); import zipObject = require('lodash/zipObject');
import { getMetaTables } from './DocApi';
bluebird.promisifyAll(tmp); bluebird.promisifyAll(tmp);
@ -2791,7 +2792,6 @@ export function tableIdToRef(metaTables: { [p: string]: TableDataAction }, table
// Helper that converts a Grist column colId to a ref given the corresponding table. // Helper that converts a Grist column colId to a ref given the corresponding table.
export function colIdToRef(metaTables: {[p: string]: TableDataAction}, tableId: string, colId: string) { export function colIdToRef(metaTables: {[p: string]: TableDataAction}, tableId: string, colId: string) {
const tableRef = tableIdToRef(metaTables, tableId); const tableRef = tableIdToRef(metaTables, tableId);
const [, , colRefs, columnData] = metaTables._grist_Tables_column; const [, , colRefs, columnData] = metaTables._grist_Tables_column;
@ -2804,6 +2804,31 @@ export function colIdToRef(metaTables: {[p: string]: TableDataAction}, tableId:
return colRefs[colRowIndex]; return colRefs[colRowIndex];
} }
// Helper that check if tableRef is used instead of tableId and return real tableId
// If metaTables is not define, activeDoc and req allow it to be created
interface MetaTables {
metaTables: { [p: string]: TableDataAction }
}
interface ActiveDocAndReq {
activeDoc: ActiveDoc, req: RequestWithLogin
}
export async function getRealTableId(
tableId: string,
options: MetaTables | ActiveDocAndReq
): Promise<string> {
if (parseInt(tableId)) {
const metaTables = "metaTables" in options
? options.metaTables
: await getMetaTables(options.activeDoc, options.req);
const [, , tableRefs, tableData] = metaTables._grist_Tables;
if (tableRefs.indexOf(parseInt(tableId)) >= 0) {
const tableRowIndex = tableRefs.indexOf(parseInt(tableId));
return tableData.tableId[tableRowIndex]!.toString();
}
}
return tableId;
}
export function sanitizeApplyUAOptions(options?: ApplyUAOptions): ApplyUAOptions { export function sanitizeApplyUAOptions(options?: ApplyUAOptions): ApplyUAOptions {
return pick(options||{}, ['desc', 'otherId', 'linkId', 'parseStrings']); return pick(options||{}, ['desc', 'otherId', 'linkId', 'parseStrings']);
} }

@ -32,7 +32,7 @@ import {
TableOperationsImpl, TableOperationsImpl,
TableOperationsPlatform TableOperationsPlatform
} from 'app/plugin/TableOperationsImpl'; } from 'app/plugin/TableOperationsImpl';
import {ActiveDoc, colIdToRef as colIdToReference, tableIdToRef} from "app/server/lib/ActiveDoc"; import {ActiveDoc, colIdToRef as colIdToReference, getRealTableId, tableIdToRef} from "app/server/lib/ActiveDoc";
import {appSettings} from "app/server/lib/AppSettings"; import {appSettings} from "app/server/lib/AppSettings";
import {sendForCompletion} from 'app/server/lib/Assistance'; import {sendForCompletion} from 'app/server/lib/Assistance';
import { import {
@ -201,7 +201,7 @@ export class DocWorkerApi {
if (!Object.keys(filters).every(col => Array.isArray(filters[col]))) { if (!Object.keys(filters).every(col => Array.isArray(filters[col]))) {
throw new ApiError("Invalid query: filter values must be arrays", 400); throw new ApiError("Invalid query: filter values must be arrays", 400);
} }
const tableId = optTableId || req.params.tableId; const tableId = await getRealTableId(optTableId || req.params.tableId, {activeDoc, req});
const session = docSessionFromRequest(req); const session = docSessionFromRequest(req);
const {tableData} = await handleSandboxError(tableId, [], activeDoc.fetchQuery( const {tableData} = await handleSandboxError(tableId, [], activeDoc.fetchQuery(
session, {tableId, filters}, !immediate)); session, {tableId, filters}, !immediate));
@ -262,11 +262,6 @@ export class DocWorkerApi {
}) })
); );
async function getMetaTables(activeDoc: ActiveDoc, req: RequestWithLogin) {
return await handleSandboxError("", [],
activeDoc.fetchMetaTables(docSessionFromRequest(req)));
}
const registerWebhook = async (activeDoc: ActiveDoc, req: RequestWithLogin, webhook: WebhookFields) => { const registerWebhook = async (activeDoc: ActiveDoc, req: RequestWithLogin, webhook: WebhookFields) => {
const {fields, url} = await getWebhookSettings(activeDoc, req, null, webhook); const {fields, url} = await getWebhookSettings(activeDoc, req, null, webhook);
if (!fields.eventTypes?.length) { if (!fields.eventTypes?.length) {
@ -337,7 +332,8 @@ export class DocWorkerApi {
const trigger = webhookId ? activeDoc.triggers.getWebhookTriggerRecord(webhookId) : undefined; const trigger = webhookId ? activeDoc.triggers.getWebhookTriggerRecord(webhookId) : undefined;
let currentTableId = trigger ? tablesTable.getValue(trigger.tableRef, 'tableId')! : undefined; let currentTableId = trigger ? tablesTable.getValue(trigger.tableRef, 'tableId')! : undefined;
const {url, eventTypes, isReadyColumn, name} = webhook; const {url, eventTypes, isReadyColumn, name} = webhook;
const tableId = req.params.tableId || webhook.tableId; const tableId = await getRealTableId(req.params.tableId || webhook.tableId, {metaTables});
const fields: Partial<SchemaTypes['_grist_Triggers']> = {}; const fields: Partial<SchemaTypes['_grist_Triggers']> = {};
if (url && !isUrlAllowed(url)) { if (url && !isUrlAllowed(url)) {
@ -387,7 +383,7 @@ export class DocWorkerApi {
// Get the columns of the specified table in recordish format // Get the columns of the specified table in recordish format
this._app.get('/api/docs/:docId/tables/:tableId/columns', canView, this._app.get('/api/docs/:docId/tables/:tableId/columns', canView,
withDoc(async (activeDoc, req, res) => { withDoc(async (activeDoc, req, res) => {
const tableId = req.params.tableId; const tableId = await getRealTableId(req.params.tableId, {activeDoc, req});
const includeHidden = isAffirmative(req.query.hidden); const includeHidden = isAffirmative(req.query.hidden);
const columns = await handleSandboxError('', [], const columns = await handleSandboxError('', [],
activeDoc.getTableCols(docSessionFromRequest(req), tableId, includeHidden)); activeDoc.getTableCols(docSessionFromRequest(req), tableId, includeHidden));
@ -498,7 +494,7 @@ export class DocWorkerApi {
withDoc(async (activeDoc, req, res) => { withDoc(async (activeDoc, req, res) => {
const colValues = req.body as BulkColValues; const colValues = req.body as BulkColValues;
const count = colValues[Object.keys(colValues)[0]].length; const count = colValues[Object.keys(colValues)[0]].length;
const op = getTableOperations(req, activeDoc); const op = await getTableOperations(req, activeDoc);
const ids = await op.addRecords(count, colValues); const ids = await op.addRecords(count, colValues);
res.json(ids); res.json(ids);
}) })
@ -527,7 +523,7 @@ export class DocWorkerApi {
} }
} }
validateCore(RecordsPost, req, body); validateCore(RecordsPost, req, body);
const ops = getTableOperations(req, activeDoc); const ops = await getTableOperations(req, activeDoc);
const records = await ops.create(body.records); const records = await ops.create(body.records);
res.json({records}); res.json({records});
}) })
@ -558,7 +554,7 @@ export class DocWorkerApi {
this._app.post('/api/docs/:docId/tables/:tableId/columns', canEdit, validate(ColumnsPost), this._app.post('/api/docs/:docId/tables/:tableId/columns', canEdit, validate(ColumnsPost),
withDoc(async (activeDoc, req, res) => { withDoc(async (activeDoc, req, res) => {
const body = req.body as Types.ColumnsPost; const body = req.body as Types.ColumnsPost;
const {tableId} = req.params; const tableId = await getRealTableId(req.params.tableId, {activeDoc, req});
const actions = body.columns.map(({fields, id: colId}) => const actions = body.columns.map(({fields, id: colId}) =>
// AddVisibleColumn adds the column to all widgets of the table. // AddVisibleColumn adds the column to all widgets of the table.
// This isn't necessarily what the user wants, but it seems like a good default. // This isn't necessarily what the user wants, but it seems like a good default.
@ -590,7 +586,7 @@ export class DocWorkerApi {
this._app.post('/api/docs/:docId/tables/:tableId/data/delete', canEdit, withDoc(async (activeDoc, req, res) => { this._app.post('/api/docs/:docId/tables/:tableId/data/delete', canEdit, withDoc(async (activeDoc, req, res) => {
const rowIds = req.body; const rowIds = req.body;
const op = getTableOperations(req, activeDoc); const op = await getTableOperations(req, activeDoc);
await op.destroy(rowIds); await op.destroy(rowIds);
res.json(null); res.json(null);
})); }));
@ -659,7 +655,7 @@ export class DocWorkerApi {
const rowIds = columnValues.id; const rowIds = columnValues.id;
// sandbox expects no id column // sandbox expects no id column
delete columnValues.id; delete columnValues.id;
const ops = getTableOperations(req, activeDoc); const ops = await getTableOperations(req, activeDoc);
await ops.updateRecords(columnValues, rowIds); await ops.updateRecords(columnValues, rowIds);
res.json(null); res.json(null);
}) })
@ -669,7 +665,7 @@ export class DocWorkerApi {
this._app.patch('/api/docs/:docId/tables/:tableId/records', canEdit, validate(RecordsPatch), this._app.patch('/api/docs/:docId/tables/:tableId/records', canEdit, validate(RecordsPatch),
withDoc(async (activeDoc, req, res) => { withDoc(async (activeDoc, req, res) => {
const body = req.body as Types.RecordsPatch; const body = req.body as Types.RecordsPatch;
const ops = getTableOperations(req, activeDoc); const ops = await getTableOperations(req, activeDoc);
await ops.update(body.records); await ops.update(body.records);
res.json(null); res.json(null);
}) })
@ -680,7 +676,7 @@ export class DocWorkerApi {
withDoc(async (activeDoc, req, res) => { withDoc(async (activeDoc, req, res) => {
const tablesTable = activeDoc.docData!.getMetaTable("_grist_Tables"); const tablesTable = activeDoc.docData!.getMetaTable("_grist_Tables");
const columnsTable = activeDoc.docData!.getMetaTable("_grist_Tables_column"); const columnsTable = activeDoc.docData!.getMetaTable("_grist_Tables_column");
const {tableId} = req.params; const tableId = await getRealTableId(req.params.tableId, {activeDoc, req});
const tableRef = tablesTable.findMatchingRowId({tableId}); const tableRef = tablesTable.findMatchingRowId({tableId});
if (!tableRef) { if (!tableRef) {
throw new ApiError(`Table not found "${tableId}"`, 404); throw new ApiError(`Table not found "${tableId}"`, 404);
@ -693,7 +689,7 @@ export class DocWorkerApi {
} }
return {...col, id}; return {...col, id};
}); });
const ops = getTableOperations(req, activeDoc, "_grist_Tables_column"); const ops = await getTableOperations(req, activeDoc, "_grist_Tables_column");
await ops.update(columns); await ops.update(columns);
res.json(null); res.json(null);
}) })
@ -711,7 +707,7 @@ export class DocWorkerApi {
} }
return {...table, id}; return {...table, id};
}); });
const ops = getTableOperations(req, activeDoc, "_grist_Tables"); const ops = await getTableOperations(req, activeDoc, "_grist_Tables");
await ops.update(tables); await ops.update(tables);
res.json(null); res.json(null);
}) })
@ -720,7 +716,7 @@ export class DocWorkerApi {
// Add or update records given in records format // Add or update records given in records format
this._app.put('/api/docs/:docId/tables/:tableId/records', canEdit, validate(RecordsPut), this._app.put('/api/docs/:docId/tables/:tableId/records', canEdit, validate(RecordsPut),
withDoc(async (activeDoc, req, res) => { withDoc(async (activeDoc, req, res) => {
const ops = getTableOperations(req, activeDoc); const ops = await getTableOperations(req, activeDoc);
const body = req.body as Types.RecordsPut; const body = req.body as Types.RecordsPut;
const options = { const options = {
add: !isAffirmative(req.query.noadd), add: !isAffirmative(req.query.noadd),
@ -740,7 +736,7 @@ export class DocWorkerApi {
withDoc(async (activeDoc, req, res) => { withDoc(async (activeDoc, req, res) => {
const tablesTable = activeDoc.docData!.getMetaTable("_grist_Tables"); const tablesTable = activeDoc.docData!.getMetaTable("_grist_Tables");
const columnsTable = activeDoc.docData!.getMetaTable("_grist_Tables_column"); const columnsTable = activeDoc.docData!.getMetaTable("_grist_Tables_column");
const {tableId} = req.params; const tableId = await getRealTableId(req.params.tableId, {activeDoc, req});
const tableRef = tablesTable.findMatchingRowId({tableId}); const tableRef = tablesTable.findMatchingRowId({tableId});
if (!tableRef) { if (!tableRef) {
throw new ApiError(`Table not found "${tableId}"`, 404); throw new ApiError(`Table not found "${tableId}"`, 404);
@ -785,7 +781,8 @@ export class DocWorkerApi {
this._app.delete('/api/docs/:docId/tables/:tableId/columns/:colId', canEdit, this._app.delete('/api/docs/:docId/tables/:tableId/columns/:colId', canEdit,
withDoc(async (activeDoc, req, res) => { withDoc(async (activeDoc, req, res) => {
const {tableId, colId} = req.params; const {colId} = req.params;
const tableId = await getRealTableId(req.params.tableId, {activeDoc, req});
const actions = [ [ 'RemoveColumn', tableId, colId ] ]; const actions = [ [ 'RemoveColumn', tableId, colId ] ];
await handleSandboxError(tableId, [colId], await handleSandboxError(tableId, [colId],
activeDoc.applyUserActions(docSessionFromRequest(req), actions) activeDoc.applyUserActions(docSessionFromRequest(req), actions)
@ -1941,12 +1938,21 @@ function getErrorPlatform(tableId: string): TableOperationsPlatform {
}; };
} }
function getTableOperations(req: RequestWithLogin, activeDoc: ActiveDoc, tableId?: string): TableOperationsImpl { export async function getMetaTables(activeDoc: ActiveDoc, req: RequestWithLogin) {
return await handleSandboxError("", [],
activeDoc.fetchMetaTables(docSessionFromRequest(req)));
}
async function getTableOperations(
req: RequestWithLogin,
activeDoc: ActiveDoc,
tableId?: string): Promise<TableOperationsImpl> {
const options: OpOptions = { const options: OpOptions = {
parseStrings: !isAffirmative(req.query.noparse) parseStrings: !isAffirmative(req.query.noparse)
}; };
const realTableId = await getRealTableId(tableId ?? req.params.tableId, {activeDoc, req});
const platform: TableOperationsPlatform = { const platform: TableOperationsPlatform = {
...getErrorPlatform(tableId ?? req.params.tableId), ...getErrorPlatform(realTableId),
applyUserActions(actions, opts) { applyUserActions(actions, opts) {
if (!activeDoc) { throw new Error('no document'); } if (!activeDoc) { throw new Error('no document'); }
return activeDoc.applyUserActions( return activeDoc.applyUserActions(

@ -547,9 +547,7 @@ function testDocApi() {
} }
it("GET /docs/{did}/tables/{tid}/data retrieves data in column format", async function () { it("GET /docs/{did}/tables/{tid}/data retrieves data in column format", async function () {
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/data`, chimpy); const data = {
assert.equal(resp.status, 200);
assert.deepEqual(resp.data, {
id: [1, 2, 3, 4], id: [1, 2, 3, 4],
A: ['hello', '', '', ''], A: ['hello', '', '', ''],
B: ['', 'world', '', ''], B: ['', 'world', '', ''],
@ -557,68 +555,73 @@ function testDocApi() {
D: [null, null, null, null], D: [null, null, null, null],
E: ['HELLO', '', '', ''], E: ['HELLO', '', '', ''],
manualSort: [1, 2, 3, 4] manualSort: [1, 2, 3, 4]
}); };
const respWithTableId = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/data`, chimpy);
assert.equal(respWithTableId.status, 200);
assert.deepEqual(respWithTableId.data, data);
const respWithTableRef = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/1/data`, chimpy);
assert.equal(respWithTableRef.status, 200);
assert.deepEqual(respWithTableRef.data, data);
}); });
it("GET /docs/{did}/tables/{tid}/records retrieves data in records format", async function () { it("GET /docs/{did}/tables/{tid}/records retrieves data in records format", async function () {
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/records`, chimpy); const data = {
assert.equal(resp.status, 200); records:
assert.deepEqual(resp.data, [
{ {
records: id: 1,
[ fields: {
{ A: 'hello',
id: 1, B: '',
fields: { C: '',
A: 'hello', D: null,
B: '', E: 'HELLO',
C: '',
D: null,
E: 'HELLO',
},
}, },
{ },
id: 2, {
fields: { id: 2,
A: '', fields: {
B: 'world', A: '',
C: '', B: 'world',
D: null, C: '',
E: '', D: null,
}, E: '',
}, },
{ },
id: 3, {
fields: { id: 3,
A: '', fields: {
B: '', A: '',
C: '', B: '',
D: null, C: '',
E: '', D: null,
}, E: '',
}, },
{ },
id: 4, {
fields: { id: 4,
A: '', fields: {
B: '', A: '',
C: '', B: '',
D: null, C: '',
E: '', D: null,
}, E: '',
}, },
] },
}); ]
};
const respWithTableId = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/records`, chimpy);
assert.equal(respWithTableId.status, 200);
assert.deepEqual(respWithTableId.data, data);
const respWithTableRef = await axios.get(
`${serverUrl}/api/docs/${docIds.Timesheets}/tables/1/records`, chimpy);
assert.equal(respWithTableRef.status, 200);
assert.deepEqual(respWithTableRef.data, data);
}); });
it('GET /docs/{did}/tables/{tid}/records honors the "hidden" param', async function () { it('GET /docs/{did}/tables/{tid}/records honors the "hidden" param', async function () {
const params = { hidden: true }; const params = { hidden: true };
const resp = await axios.get( const data = {
`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/records`,
{...chimpy, params }
);
assert.equal(resp.status, 200);
assert.deepEqual(resp.data.records[0], {
id: 1, id: 1,
fields: { fields: {
manualSort: 1, manualSort: 1,
@ -628,7 +631,19 @@ function testDocApi() {
D: null, D: null,
E: 'HELLO', E: 'HELLO',
}, },
}); };
const respWithTableId = await axios.get(
`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/records`,
{...chimpy, params }
);
assert.equal(respWithTableId.status, 200);
assert.deepEqual(respWithTableId.data.records[0], data);
const respWithTableRef = await axios.get(
`${serverUrl}/api/docs/${docIds.Timesheets}/tables/1/records`,
{...chimpy, params }
);
assert.equal(respWithTableRef.status, 200);
assert.deepEqual(respWithTableRef.data.records[0], data);
}); });
it("GET /docs/{did}/tables/{tid}/records handles errors and hidden columns", async function () { it("GET /docs/{did}/tables/{tid}/records handles errors and hidden columns", async function () {
@ -683,119 +698,121 @@ function testDocApi() {
}); });
it("GET /docs/{did}/tables/{tid}/columns retrieves columns", async function () { it("GET /docs/{did}/tables/{tid}/columns retrieves columns", async function () {
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/columns`, chimpy); const data = {
assert.equal(resp.status, 200); columns: [
assert.deepEqual(resp.data, {
{ id: 'A',
columns: [ fields: {
{ colRef: 2,
id: 'A', parentId: 1,
fields: { parentPos: 1,
colRef: 2, type: 'Text',
parentId: 1, widgetOptions: '',
parentPos: 1, isFormula: false,
type: 'Text', formula: '',
widgetOptions: '', label: 'A',
isFormula: false, description: '',
formula: '', untieColIdFromLabel: false,
label: 'A', summarySourceCol: 0,
description: '', displayCol: 0,
untieColIdFromLabel: false, visibleCol: 0,
summarySourceCol: 0, rules: null,
displayCol: 0, recalcWhen: 0,
visibleCol: 0, recalcDeps: null
rules: null,
recalcWhen: 0,
recalcDeps: null
}
},
{
id: 'B',
fields: {
colRef: 3,
parentId: 1,
parentPos: 2,
type: 'Text',
widgetOptions: '',
isFormula: false,
formula: '',
label: 'B',
description: '',
untieColIdFromLabel: false,
summarySourceCol: 0,
displayCol: 0,
visibleCol: 0,
rules: null,
recalcWhen: 0,
recalcDeps: null
}
},
{
id: 'C',
fields: {
colRef: 4,
parentId: 1,
parentPos: 3,
type: 'Text',
widgetOptions: '',
isFormula: false,
formula: '',
label: 'C',
description: '',
untieColIdFromLabel: false,
summarySourceCol: 0,
displayCol: 0,
visibleCol: 0,
rules: null,
recalcWhen: 0,
recalcDeps: null
}
},
{
id: 'D',
fields: {
colRef: 5,
parentId: 1,
parentPos: 3,
type: 'Any',
widgetOptions: '',
isFormula: true,
formula: '',
label: 'D',
description: '',
untieColIdFromLabel: false,
summarySourceCol: 0,
displayCol: 0,
visibleCol: 0,
rules: null,
recalcWhen: 0,
recalcDeps: null
}
},
{
id: 'E',
fields: {
colRef: 6,
parentId: 1,
parentPos: 4,
type: 'Any',
widgetOptions: '',
isFormula: true,
formula: '$A.upper()',
label: 'E',
description: '',
untieColIdFromLabel: false,
summarySourceCol: 0,
displayCol: 0,
visibleCol: 0,
rules: null,
recalcWhen: 0,
recalcDeps: null
}
} }
] },
} {
); id: 'B',
fields: {
colRef: 3,
parentId: 1,
parentPos: 2,
type: 'Text',
widgetOptions: '',
isFormula: false,
formula: '',
label: 'B',
description: '',
untieColIdFromLabel: false,
summarySourceCol: 0,
displayCol: 0,
visibleCol: 0,
rules: null,
recalcWhen: 0,
recalcDeps: null
}
},
{
id: 'C',
fields: {
colRef: 4,
parentId: 1,
parentPos: 3,
type: 'Text',
widgetOptions: '',
isFormula: false,
formula: '',
label: 'C',
description: '',
untieColIdFromLabel: false,
summarySourceCol: 0,
displayCol: 0,
visibleCol: 0,
rules: null,
recalcWhen: 0,
recalcDeps: null
}
},
{
id: 'D',
fields: {
colRef: 5,
parentId: 1,
parentPos: 3,
type: 'Any',
widgetOptions: '',
isFormula: true,
formula: '',
label: 'D',
description: '',
untieColIdFromLabel: false,
summarySourceCol: 0,
displayCol: 0,
visibleCol: 0,
rules: null,
recalcWhen: 0,
recalcDeps: null
}
},
{
id: 'E',
fields: {
colRef: 6,
parentId: 1,
parentPos: 4,
type: 'Any',
widgetOptions: '',
isFormula: true,
formula: '$A.upper()',
label: 'E',
description: '',
untieColIdFromLabel: false,
summarySourceCol: 0,
displayCol: 0,
visibleCol: 0,
rules: null,
recalcWhen: 0,
recalcDeps: null
}
}
]
};
const respWithTableId = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/Table1/columns`, chimpy);
assert.equal(respWithTableId.status, 200);
assert.deepEqual(respWithTableId.data, data);
const respWithTableRef = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/1/columns`, chimpy);
assert.equal(respWithTableRef.status, 200);
assert.deepEqual(respWithTableRef.data, data);
}); });
it('GET /docs/{did}/tables/{tid}/columns retrieves hidden columns when "hidden" is set', async function () { it('GET /docs/{did}/tables/{tid}/columns retrieves hidden columns when "hidden" is set', async function () {
@ -869,6 +886,13 @@ function testDocApi() {
] ]
}); });
// POST /columns: Create new columns using tableRef in URL
resp = await axios.post(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/5/columns`, {
columns: [{id: "NewCol6", fields: {}}],
}, chimpy);
assert.equal(resp.status, 200);
assert.deepEqual(resp.data, {columns: [{id: "NewCol6"}]});
// POST /columns to invalid table ID // POST /columns to invalid table ID
resp = await axios.post(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/NoSuchTable/columns`, resp = await axios.post(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/NoSuchTable/columns`,
{columns: [{}]}, chimpy); {columns: [{}]}, chimpy);
@ -1032,6 +1056,7 @@ function testDocApi() {
{colId: "NewCol4", label: 'NewCol4'}, {colId: "NewCol4", label: 'NewCol4'},
{colId: "NewCol4_2", label: 'NewCol4_2'}, {colId: "NewCol4_2", label: 'NewCol4_2'},
// NewCol5 is hidden by ACL // NewCol5 is hidden by ACL
{colId: "NewCol6", label: 'NewCol6'},
]); ]);
resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/NewTable2_2/columns`, chimpy); resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/tables/NewTable2_2/columns`, chimpy);

Loading…
Cancel
Save