diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 19887b2f..e0b2281d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,19 +1,46 @@ -name: Deploy developer.setable.io (mkdocs material based with github pages) +name: Deploy developer.seatable.com (mkdocs material with github pages) on: push: branches: - main pull_request: + branches: + - main + +env: + GO_VERSION: 1.24.1 + MUFFET_VERSION: v2.10.7 permissions: contents: write jobs: + check-links: + name: Check links + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - uses: actions/setup-go@v2 + with: + go-version: ${{ env.GO_VERSION }} + - name: Start development server + run: ./preview.sh + - name: Add $GOPATH/bin to $PATH + run: | + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + - name: Install muffet + run: go install github.com/raviqqe/muffet/v2@${MUFFET_VERSION} + - name: Check links + run: muffet -t60 -c10 -f -i 'http://127*' -i 'http://localhost' http://127.0.0.1:8000 deploy: runs-on: ubuntu-latest + needs: + - check-links + if: github.ref == 'refs/heads/main' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -31,5 +58,4 @@ jobs: run: mkdocs build --strict --verbose - name: gh-deploy - if: github.ref == 'refs/heads/main' run: mkdocs gh-deploy --force diff --git a/docs/clients/index.md b/docs/clients/index.md deleted file mode 100644 index 7bde4ec9..00000000 --- a/docs/clients/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Client APIs - -Thanks to SeaTable's full API, virtually anything can be controlled with any programming language. - -On the [API Reference](https://api.seatable.com) you can find all available API commands and sample codes for different programming languages. - -For a few programming languages there are already ready-to-use client APIs classes that do some of the work for you. These are presented in this part of the documentation. diff --git a/docs/clients/javascript/columns.md b/docs/clients/javascript/columns.md deleted file mode 100644 index a436b71e..00000000 --- a/docs/clients/javascript/columns.md +++ /dev/null @@ -1,165 +0,0 @@ -# Columns - -Every table in a base contains columns. The following calls are available to interact with the columns of a table. - -## Get Columns - -!!! question "listColumns" - - ```js - base.listColumns(table_name, (view_name = "")); - ``` - - **Example** - - ```js - const columns1 = await base.listColumns("Table1"); - const columns2 = await base.listColumns("Table1", (view_name = "default")); - ``` - -!!! question "getColumnByName" - - ``` js - base.getColumnByName(table_name, column_name); - ``` - - __Example__ - ``` js - const col = await base.getColumnByName('Table1', 'Name'); - ``` - -!!! question "getColumnsByType" - - ``` js - base.getColumnsByType(table_name, col_type); - ``` - - __Example__ - ``` js - const cols = await base.getColumnsByType('Table1', 'number') - ``` - -## Add Column - -!!! question "insertColumn" - - ``` js - base.insertColumn(table_name, column_name, column_type, column_key='', column_data='') - ``` - - __Example__ - ``` js - import { ColumnTypes } from 'seatable-api'; - await base.insertColumn('Table1', 'seatable-api', ColumnTypes.TEXT) - await base.insertColumn('Table1', 'seatable-api', ColumnTypes.TEXT, '0000') - await base.insertColumn('Table1', 'Link1', ColumnTypes.LINK, column_data={ - 'table':'Table1', - 'other_table':'Test_User' - }) - ``` - -## Rename Column - -!!! question "renameColumn" - - ``` js - base.renameColumn(table_name, column_key, new_column_name) - ``` - - __Example__ - ``` js - await base.renameColumn('Table1', 'kSiR', 'new-seatable-api') - ``` - -## Column Settings - -!!! question "resizeColumn" - - ``` js - base.resizeColumn(table_name, column_key, new_column_width) - ``` - - __Example__ - ``` js - await base.resizeColumn('Table1', 'asFV', 500) - ``` - -!!! question "freezeColumn" - - ``` js - base.freezeColumn(table_name, column_key, frozen) - ``` - - __Example__ - ``` js - await base.freezeColumn('Table1', '0000', true) - ``` - -!!! question "moveColumn" - - ``` js - base.moveColumn(table_name, column_key, target_column_key) - ``` - - __Example__ - In this example, the 'loPx' column will be moved to the right of the '0000' column - ``` js - await base.moveColumn('Table1', 'loPx', '0000') - ``` - -!!! question "modifyColumnType" - - ``` js - base.modifyColumnType(table_name, column_key, new_column_type) - ``` - - __Example__ - ``` js - import { ColumnTypes } from 'seatable-api'; - await base.modifyColumnType('Table1', 'nePI', ColumnTypes.NUMBER) - ``` - -!!! question "addColumnOptions" - - Used by "single select" or "multiple select"-type columns - ``` js - base.addColumnOptions(table_name, column, options) - ``` - - __Example__ - ``` js - await base.addColumnOptions('Table1', 'My choices', [ - {"name": "ddd", "color": "#aaa", "textColor": "#000000"}, - {"name": "eee", "color": "#aaa", "textColor": "#000000"}, - {"name": "fff", "color": "#aaa", "textColor": "#000000"}, - ]) - ``` - -!!! question "addColumnCascadeSettings" - - Used by "single select"-type column, to add a limitation of child column options according to the option of parent column - ``` js - base.addColumnCascadeSettings(table_name, child_column, parent_column, cascade_settings) - ``` - - __Example__ - ``` js - await base.addColumnCascadeSettings("Table1", "single-op-col-c", "single-op-col", { - "aaa": ["aaa-1", "aaa-2"], # If “aaa” is selected by parent column, the available options of child column are "aaa-1 and aaa-2" - "bbb": ["bbb-1", "bbb-2"], - "ccc": ["ccc-1", "ccc-2"] - }) - ``` - -## Delete Column - -!!! question "deleteColumn" - - ``` js - base.deleteColumn(table_name, column_key) - ``` - - __Example__ - ``` js - await base.deleteColumn('Table1', 'bsKL') - ``` diff --git a/docs/clients/javascript/constants.md b/docs/clients/javascript/constants.md deleted file mode 100644 index 96f40d1a..00000000 --- a/docs/clients/javascript/constants.md +++ /dev/null @@ -1,30 +0,0 @@ -# Constants - -In the script there may be some constants we need to know. - -!!! question "ColumnTypes" - - Column type, when insert/add columns, change column types, etc. need to be used - ``` js - import { ColumnTypes } from 'seatable-api'; - - ColumnTypes.NUMBER // number - ColumnTypes.TEXT // text - ColumnTypes.LONG_TEXT // long text - ColumnTypes.CHECKBOX // checkbox - ColumnTypes.DATE // date & time - ColumnTypes.SINGLE_SELECT // single select - ColumnTypes.MULTIPLE_SELECT // multiple - ColumnTypes.IMAGE // image - ColumnTypes.FILE // file - ColumnTypes.COLLABORATOR // collaborator - ColumnTypes.LINK // link to - ColumnTypes.FORMULA // formula - ColumnTypes.CREATOR // creator - ColumnTypes.CTIME // create time - ColumnTypes.LAST_MODIFIER // last modifier - ColumnTypes.MTIME // modify time - ColumnTypes.GEOLOCATION // geolocation - ColumnTypes.AUTO_NUMBER // auto number - ColumnTypes.URL // URL - ``` diff --git a/docs/clients/javascript/javascript_api.md b/docs/clients/javascript/javascript_api.md deleted file mode 100644 index c4aa562b..00000000 --- a/docs/clients/javascript/javascript_api.md +++ /dev/null @@ -1,66 +0,0 @@ -# JavaScript client - -The SeaTable JavaScript client encapsulates SeaTable Server Restful API. You can call it in your front-end page or Node.js program. - -!!! warning "Two different clients" - JavaScript API cannot be used for scripts in SeaTable bases. For script programming with JavaScript, there is a [separate chapter](../../scripts/javascript/objects/index.md) in this documentation. - -Note that JavaScript API calls SeaTable Server Restful API, whereas scripts in SeaTable bases interact with the base loaded in the browser, so the APIs of the two are somewhat different. - -## Installation - -```shell -npm install seatable-api -``` - -The source code of the JavaScript Client API is available at [GitHub](https://github.com/seatable/seatable-api-js). - -## Reference - -To use SeaTable APIs, you should first initialize a base object and call `base.auth()`. `base.auth()` is an async function, which needs to be executed in async functions. Other APIs all return a promise object. There are two ways to use them: - -=== "First way using then" - - ```js - base.listViews(tableName).then(views => { - // Use views to complete the requirements - }).catch(error => { - // Exception handling - }) - ``` - -=== "Second way using await" - - ```js - try { - const views = await base.listViews(tableName); - // Use views to complete the requirements - } catch (error) { - // Exception handling - } - ``` - -Here are the main SeaTable API errors you might encounter: - -- 400 Params invalid -- 403 Permission denied -- 413 Exceed limit (see the [API Reference](https://api.seatable.com/reference/limits) about limits) -- 500 Internal Server Error - -## Authorization - -The `Base` object represents a table. You need to specify an `APIToken` to get access authorization and to be able to read and write the base. API tokens can be directly [generated in the web interface](https://seatable.com/help/erzeugen-eines-api-tokens/). - -__Example__ - -```js -import { Base } from "seatable-api"; - -const config = { - server: "https://cloud.seatable.cn", - APIToken: "c3c75dca2c369849455a39f4436147639cf02b2d", -}; - -const base = new Base(config); -await base.auth(); -``` diff --git a/docs/clients/javascript/links.md b/docs/clients/javascript/links.md deleted file mode 100644 index ff384427..00000000 --- a/docs/clients/javascript/links.md +++ /dev/null @@ -1,131 +0,0 @@ -# Links - -## Get Links - -!!! question "getLinkedRecords" - - List the linked records of rows. You can get the linked records of multiple rows. - - ```js - base.getColumnLinkId(table_name, column_name); - ``` - - **Example** - - ```js - await base.getLinkedRecords('0000', '89o4', [ - {'row_id': 'FzNqJxVUT8KrRjewBkPp8Q', 'limit': 2, 'offset': 0}, - {'row_id': 'Jmnrkn6TQdyRg1KmOM4zZg', 'limit': 20} - ]) - - // a key-value data structure returned as below - // key: row_id of link table - // value: a list which includes the row info of linked table - { - 'FzNqJxVUT8KrRjewBkPp8Q': [ - {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, - {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, - ... - ], - 'Jmnrkn6TQdyRg1KmOM4zZg': [ - {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, - {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, - ... - ] - } - ``` - -## Get Link ID - -!!! question "Get Link ID" - - ```js - base.getColumnLinkId(table_name, column_name); - ``` - - **Example** - - ```js - const linkId = await base.getColumnLinkId("LinkTable", "Record"); - // return the link id such as 'aHL2' - ``` - -## Add Link - -!!! question "addLink" - - Add links, link other table records - - ```js - base.addLink(link_id, table_name, other_table_name, row_id, other_row_id); - ``` - - **Example** - - ```js - await base.addLink( - "5WeC", - "real-img-files", - "contact", - "CGtoJB1oQM60RiKT-c5J-g", - "PALm2wPKTCy-jdJNv_UWaQ" - ); - ``` - -## Update Link(s) - -!!! question "updateLink" - - Modify the info of link-type column - ``` js - base.updateLink(link_id, table_name, other_table_name, row_id, other_rows_ids) - ``` - - __Example__ - ``` js - await base.updateLink( - link_id='r4IJ', - table_name='Table1', - other_table_name='Table2', - row_id='BXhEm9ucTNu3FjupIk7Xug', - other_rows_ids=[ - 'exkb56fAT66j8R0w6wD9Qg', - 'DjHjwmlRRB6WgU9uPnrWeA' - ] - ) - ``` - -!!! question "batchUpdateLinks" - - Batch update infos of link-type columns - ``` js - base.batchUpdateLinks(link_id, table_name, other_table_name, row_id_list, other_rows_ids_map) - ``` - - __Example__ - ``` js - link_id = "WaW5" - table_name ="Table1" - other_table_name = "Table2" - row_id_list = ["fRLglslWQYSGmkU7o6KyHw","eSQe9OpPQxih8A9zPXdMVA","FseN8ygVTzq1CHDqI4NjjQ"] - other_rows_ids_map = { - "FseN8ygVTzq1CHDqI4NjjQ":["OcCE8aX8T7a4dvJr-qNh3g","JckTyhN0TeS8yvH8D3EN7g"], - "eSQe9OpPQxih8A9zPXdMVA":["cWHbzQiTR8uHHzH_gVSKIg","X56gE7BrRF-i61YlE4oTcw"], - "fRLglslWQYSGmkU7o6KyHw":["MdfUQiWcTL--uMlrGtqqgw","E7Sh3FboSPmfBlDsrj_Fhg","UcZ7w9wDT-uVq4Ohtwgy9w"] - } - - await base.batchUpdateLinks(link_id, table_id, other_table_id, row_id_list, other_rows_ids_map) - ``` - -## Remove Link - -!!! question "removeLink" - - ``` js - base.removeLink(link_id, table_name, other_table_name, row_id, other_row_id) - ``` - - __Example__ - ``` js - await base.removeLink('5WeC', 'real-img-files', 'contact', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ') - ``` diff --git a/docs/clients/javascript/metadata.md b/docs/clients/javascript/metadata.md deleted file mode 100644 index f95a5267..00000000 --- a/docs/clients/javascript/metadata.md +++ /dev/null @@ -1,65 +0,0 @@ -# Metadata - -Metadata delivers the complete structure of a base with tables, views and columns. - -!!! question "getMetadata" - - Get the complete metadata of a table. The metadata will not contain the concrete rows of the table. - - ``` js - base.getMetadata(); - ``` - - __Example__ - - ``` - { - 'tables': [{ - '_id': '4krH', - 'name': 'Contact', - 'is_header_locked': false, - 'columns': [{ - 'key': '0000', - 'type': 'text', - 'name': 'Name', - 'editable': true, - 'width': 200, - 'resizable': true, - 'draggable': true, - 'data': null, - 'permission_type': '', - 'permitted_users': [] - }, { - 'key': 'M31F', - 'type': 'text', - 'name': 'Email', - 'editable': true, - 'width': 200, - 'resizable': true, - 'draggable': true, - 'data': null, - 'permission_type': '', - 'permitted_users': [] - }], - 'views': [{ - '_id': '0000', - 'name': 'Default view', - 'type': 'table', - 'is_locked': false, - 'filter_conjunction': 'And', - 'filters': [], - 'sorts': [], - 'groupbys': [], - 'group_rows': [], - 'groups': [], - 'colorbys': {}, - 'hidden_columns': [], - 'rows': [], - 'formula_rows': {}, - 'link_rows': {}, - 'summaries': {}, - 'colors': {} - }] - }] - } - ``` diff --git a/docs/clients/javascript/rows.md b/docs/clients/javascript/rows.md deleted file mode 100644 index 94aab370..00000000 --- a/docs/clients/javascript/rows.md +++ /dev/null @@ -1,160 +0,0 @@ -# Rows - -## Get Rows - -!!! question "listRows" - - ``` js - base.listRows(table_name, view_name=None, order_by='', desc='', start='', limit='') - ``` - - __Example__ - ``` js - const rows1 = await base.listRows('Table1') - const rows2 = await base.listRows('Table1', view_name='default', order_by='年龄', desc=true, start=5, limit=20) - ``` - -!!! question "getRow" - - ``` js - base.getRow(table_name, row_id) - ``` - - __Example__ - ``` js - const row = await base.getRow('Table1', 'U_eTV7mDSmSd-K2P535Wzw') - ``` - -## Add Row(s) - -!!! question "appendRow" - - ``` js - base.appendRow(table_name, row_data, apply_default = false) - ``` - - __Example__ - ``` js - row_data = { - "Name": "I am new Row" - } - - await base.appendRow('Table1', row_data) - ``` - -!!! question "insertRow" - - ``` js - base.insertRow(table_name, row_data, anchor_row_id, apply_default = false) - ``` - - __Example__ - ``` js - const row_data = { - "Name": "I am new Row" - } - - await base.insertRow('Table1', row_data, 'U_eTV7mDSmSd-K2P535Wzw') - ``` - -!!! question "batchAppendRows" - - ``` js - base.batchAppendRows(table_name, rows_data, apply_default = false) - ``` - - **Example** - - ``` js - const rows_data = [{ - 'Name': 'test batch', - 'content': 'Yes' - }, { - 'Name': 'test batch', - 'content': 'Yes' - }, { - 'Name': 'test batch', - 'content': 'Yes' - }] - - // Whether to use the default value set in the table column. - // If set to true, the default value will be used if the column is not specified in row_data. - // apply_default is set to false by default. - await base.batchAppendRows('Table6', rows_data, apply_default = true) - ``` - -## Update Row - -!!! question "updateRow" - - ``` js - base.updateRow(table_name, row_id, row_data) - ``` - - __Example__ - ``` js - row_data = { - "Number": "123" - } - await base.updateRow('Table1', 'U_eTV7mDSmSd-K2P535Wzw', row_data) - ``` - -!!! question "batchUpdateRows" - - ``` js - base.batchUpdateRows(table_name, rows_data) - ``` - - __Example__ - ``` js - const updates_data = [ - { - "row_id": "fMmCFyoxT4GN5Y2Powbl0Q", - "row": { - "Name": "Ranjiwei", - "age": "36" - } - }, - { - "row_id": "cF5JTE99Tae-VVx0BGT-3A", - "row": { - "Name": "Huitailang", - "age": "33" - } - }, - { - "row_id": "WP-8rb5PSUaM-tZRmTOCPA", - "row": { - "Name": "Yufeng", - "age": "22" - } - } - ] - await base.batchUpdateRows('Table1', rows_data=updates_data) - ``` - -## Delete Row(s) - -!!! question "deleteRow" - - ``` js - base.deleteRow(table_name, row_id) - ``` - - __Example__ - ``` js - await base.deleteRow('Table1', 'U_eTV7mDSmSd-K2P535Wzw') - ``` - -!!! question "batchDeleteRows" - - ``` js - base.batchDeleteRows(table_name, row_ids) - ``` - - __Example__ - ``` js - const del_rows = rows.slice(0, 3); - const row_ids = del_rows.map(row => row._id); - await base.batchDeleteRows('Table1', row_ids) - ``` diff --git a/docs/clients/javascript/sql_query.md b/docs/clients/javascript/sql_query.md deleted file mode 100644 index fb511da5..00000000 --- a/docs/clients/javascript/sql_query.md +++ /dev/null @@ -1,87 +0,0 @@ -# Query with SQL - -!!! question "query" - - Use sql to query a base - ``` js - base.query(sql) - ``` - - __Example: BASIC__ - ``` js - await base.query('select name, price, year from Bill') - ``` - Returns for example the following: - ``` js - [ - {'_id': 'PzBiZklNTGiGJS-4c0_VLw', 'name': 'Bob', 'price': 300, 'year': 2019}, - {'_id': 'Ep7odyv1QC2vDQR2raMvSA', 'name': 'Bob', 'price': 300, 'year': 2021}, - {'_id': 'f1x3X_8uTtSDUe9D60VlYQ', 'name': 'Tom', 'price': 100, 'year': 2019}, - {'_id': 'NxeaB5pDRFKOItUs_Ugxug', 'name': 'Tom', 'price': 100, 'year': 2020}, - {'_id': 'W0BrjGQpSES9nfSytvXgMA', 'name': 'Tom', 'price': 200, 'year': 2021}, - {'_id': 'EvwCWtX3RmKYKHQO9w2kLg', 'name': 'Jane', 'price': 200, 'year': 2020}, - {'_id': 'BTiIGSTgR06UhPLhejFctA', 'name': 'Jane', 'price': 200, 'year': 2021} - ] - ``` - - __Example: WHERE__ - - ``` js - await base.query('select name, price from Bill where year = 2021 ') - ``` - Returns for example the following: - ``` js - [ - {'_id': 'Ep7odyv1QC2vDQR2raMvSA', 'name': 'Bob', 'price': 300}, - {'_id': 'W0BrjGQpSES9nfSytvXgMA', 'name': 'Tom', 'price': 200}, - {'_id': 'BTiIGSTgR06UhPLhejFctA', 'name': 'Jane', 'price': 200} - ] - ``` - - __Example: ORDER BY__ - - ``` js - await base.query('select name, price, year from Bill order by year') - ``` - Returns for example the following: - - ``` js - [ - {'_id': 'PzBiZklNTGiGJS-4c0_VLw', 'name': 'Bob', 'price': 300, 'year': 2019}, - {'_id': 'f1x3X_8uTtSDUe9D60VlYQ', 'name': 'Tom', 'price': 100, 'year': 2019}, - {'_id': 'NxeaB5pDRFKOItUs_Ugxug', 'name': 'Tom', 'price': 100, 'year': 2020}, - {'_id': 'EvwCWtX3RmKYKHQO9w2kLg', 'name': 'Jane', 'price': 200, 'year': 2020}, - {'_id': 'Ep7odyv1QC2vDQR2raMvSA', 'name': 'Bob', 'price': 300, 'year': 2021}, - {'_id': 'W0BrjGQpSES9nfSytvXgMA', 'name': 'Tom', 'price': 200, 'year': 2021}, - {'_id': 'BTiIGSTgR06UhPLhejFctA', 'name': 'Jane', 'price': 200, 'year': 2021} - ] - ``` - - __Example: GROUP BY__ - - ``` js - await base.query('select name, sum(price) from Bill group by name') - ``` - Returns for example the following: - ``` js - [ - {'SUM(price)': 600, 'name': 'Bob'}, - {'SUM(price)': 400, 'name': 'Tom'}, - {'SUM(price)': 400, 'name': 'Jane'} - ] - ``` - - __Example: DISTINCT__ - - ``` js - await base.query('select distinct name from Bill') - ``` - Returns for example the following: - - ``` js - [ - {'_id': 'PzBiZklNTGiGJS-4c0_VLw', 'name': 'Bob'}, - {'_id': 'f1x3X_8uTtSDUe9D60VlYQ', 'name': 'Tom'}, - {'_id': 'EvwCWtX3RmKYKHQO9w2kLg', 'name': 'Jane'} - ] - ``` diff --git a/docs/clients/javascript/tables.md b/docs/clients/javascript/tables.md deleted file mode 100644 index 2947c303..00000000 --- a/docs/clients/javascript/tables.md +++ /dev/null @@ -1,64 +0,0 @@ -# Tables - -## Get Table(s) - -!!! question "getTables" - - ``` js - base.getTables() - ``` - - __Example__ - ``` js - const tables = await base.getTables(); - ``` - -!!! question "getTableByName" - - ``` js - base.getTableByName(tableName); - ``` - - __Example__ - ``` js - const table = await base.getTableByName('Table1') - ``` - -## Add Table - -!!! question "addTable" - - ``` js - base.addTable(tableName: String, lang='en', columns=[]) - ``` - - __Example__ - ``` js - await base.addTable('Investigation', lang='en') - ``` - -## Rename Table - -!!! question "renameTable" - - ``` js - base.renameTable(oldName: String, newName: String) - ``` - - __Example__ - ``` js - await base.renameTable('Table1', 'Projects 2023'); - ``` - -## Delete Table - -!!! question "deleteTable" - - ``` js - base.deleteTable(tableName) - ``` - - __Example__ - ``` js - await base.deleteTable('Table1') - ``` diff --git a/docs/clients/javascript/views.md b/docs/clients/javascript/views.md deleted file mode 100644 index 4cf2f678..00000000 --- a/docs/clients/javascript/views.md +++ /dev/null @@ -1,64 +0,0 @@ -# Views - -## Get Views - -!!! question "listViews" - - ``` js - base.listViews(table_name) - ``` - - __Example__ - ``` js - const views = await base.listViews('Table1') - ``` - -!!! question "getViewByName" - - ``` js - base.getViewByName(table_name, view_name); - ``` - - __Example__ - ``` js - const view = await base.getViewByName('Table1', 'MyView'); - ``` - -## Add View - -!!! question "addView" - - ``` js - base.addView(table_name, new_view_name); - ``` - - __Example__ - ``` js - await base.addView('Table1', 'new_view'); - ``` - -## Rename View - -!!! question "renameView" - - ``` js - base.renameView(table_name, view_name, new_view_name); - ``` - - __Example__ - ``` js - await base.renameView('Table1', 'myView', 'myView-01'); - ``` - -## Delete View - -!!! question "deleteView" - - ``` js - base.deleteView(table_name, view_name); - ``` - - __Example__ - ``` js - await base.deleteView('Table1', 'MyView'); - ``` diff --git a/docs/clients/php_api.md b/docs/clients/php_api.md deleted file mode 100644 index 44f564d5..00000000 --- a/docs/clients/php_api.md +++ /dev/null @@ -1,186 +0,0 @@ -# PHP client - -SeaTable's API exposes the entire SeaTable features via a standardized programmatic interface. The _SeaTable PHP Client_ encapsulates SeaTable Server Restful API. If you are familiar this client enables you to call every available API endpoint of SeaTable. You can interact with the user accounts, bases or files. - -!!! info "Auto generated from openapi specification" - - Since April 2024, we auto generate this SeaTable php client from our public available openapi specification. The advantage is that the PHP client automatically contains all available API endpoints and we save a lot of programming capacity. Also we could generate more api clients for other programming languages in no time with the same feature set. The disadvantage is, that with this new client we removed some convenient functions for authentication and the new version is not compatible at all with the version v0.2 and earlier. - -## Installation - -The SeaTable API installs as part of your project dependencies. It is available from [Packagist](https://packagist.org/packages/seatable/seatable-api-php) and can be installed with [Composer](https://getcomposer.org/): - -``` -composer require seatable/seatable-api-php -``` - -The source code of the PHP Client API is available at [GitHub](https://github.com/seatable/seatable-api-php). - -## Getting started - -After installation you can easily connect to your SeaTable system and execute API calls. - -### Get information about your account - -The following code connects to SeaTable Cloud. You have to provide your `Account Token`. -Please refer to [api.seatable.com](https://api.seatable.com/reference/getaccounttokenfromusername) for guidance on how to obtain an `Account Token`. - -```php -setAccessToken('YOUR_ACCOUNT_TOKEN'); - -$apiInstance = new SeaTable\Client\User\UserApi(new GuzzleHttp\Client(), $config); - -try { - $result = $apiInstance->getAccountInfo(); - print_r($result); -} catch (Exception $e) { - echo 'Exception when calling UserApi->getAccountInfo: ', $e->getMessage(), PHP_EOL; -} -``` - -### List your bases - -This time, we connect to a self-hosted SeaTable Server. - -```php -setAccessToken('YOUR_ACCOUNT_TOKEN'); -$config->setHost('https://seatable.example.com'); - -$apiInstance = new SeaTable\Client\User\BasesApi(new GuzzleHttp\Client(), $config); - -try { - $result = $apiInstance->listBases(); - print_r($result); -} catch (Exception $e) { - echo 'Exception when calling BasesApi->listBases: ', $e->getMessage(), PHP_EOL; -} -``` - -### Get Metadata from your Base - -First we have to get the `Base-Token` and the `base_uuid` and then we can execute the `getMetadata` call. - -```php -setAccessToken('YOUR_API_TOKEN'); - -$apiInstance = new SeaTable\Client\Auth\BaseTokenApi(new GuzzleHttp\Client(), $config); - -try { - $result = $apiInstance->getBaseTokenWithApiToken(); - print_r($result); -} catch (Exception $e) { - echo 'Exception when calling BaseTokenApi->getBaseTokenWithApiToken: ', $e->getMessage(), PHP_EOL; -} - -// Metadata -$config = SeaTable\Client\Configuration::getDefaultConfiguration()->setAccessToken($result['access_token']); -$apiInstance = new SeaTable\Client\Base\BaseInfoApi(new GuzzleHttp\Client(), $config); - -try { - $result = $apiInstance->getMetadata($result['dtable_uuid']); - print_r($result); -} catch (Exception $e) { - echo 'Exception when calling BaseInfoApi->getMetadata: ', $e->getMessage(), PHP_EOL; -} -``` - -### Execute SQL-Query against your base - -```php -setAccessToken('YOUR_API_TOKEN'); - -$apiInstance = new SeaTable\Client\Auth\BaseTokenApi(new GuzzleHttp\Client(), $config); - -try { - $result = $apiInstance->getBaseTokenWithApiToken(); - print_r($result); -} catch (Exception $e) { - echo 'Exception when calling BaseTokenApi->getBaseTokenWithApiToken: ', $e->getMessage(), PHP_EOL; -} - -// Base query -$config = SeaTable\Client\Configuration::getDefaultConfiguration()->setAccessToken($result['access_token']); - -$apiInstance = new SeaTable\Client\Base\RowsApi(new GuzzleHttp\Client(), $config); - -$base_uuid = $result['dtable_uuid']; -$sql_query = new SeaTable\Client\Base\SqlQuery(["sql" => "Select * from Table1", "convert_keys" => false]); - -try { - $result = $apiInstance->querySQL($base_uuid, $sql_query); - print_r($result); -} catch (Exception $e) { - echo 'Exception when calling RowsApi->querySQL: ', $e->getMessage(), PHP_EOL; -} -``` - -### Add a row to your base - -```php -setAccessToken('YOUR_API_TOKEN'); - -$apiInstance = new SeaTable\Client\Auth\BaseTokenApi(new GuzzleHttp\Client(), $config); - -try { - $result = $apiInstance->getBaseTokenWithApiToken(); - print_r($result); -} catch (Exception $e) { - echo 'Exception when calling BaseTokenApi->getBaseTokenWithApiToken: ', $e->getMessage(), PHP_EOL; -} - -// Base query -$config = SeaTable\Client\Configuration::getDefaultConfiguration()->setAccessToken($result['access_token']); - -$apiInstance = new SeaTable\Client\Base\RowsApi(new GuzzleHttp\Client(), $config); - -$base_uuid = $result['dtable_uuid']; -$request = new SeaTable\Client\Base\AppendRows([ - 'table_name' => 'Table1', - 'rows' => [ - [ - 'Name' => 'Inserted via API', - ], - ], - // Whether to apply default values - 'apply_default' => false, -]); - -try { - $result = $apiInstance->appendRows($base_uuid, $request); - print_r($result); -} catch (Exception $e) { - echo 'Exception when calling RowsApi->appendRows: ', $e->getMessage(), PHP_EOL; -} -``` - -## API Endpoints - -You can find detailed documentation for all endpoints including auto-generated examples on [GitHub](https://github.com/seatable/seatable-api-php): - - - [Auth](https://github.com/seatable/seatable-api-php/blob/main/README_Auth.md) - - [Base](https://github.com/seatable/seatable-api-php/blob/main/README_Base.md) - - [File](https://github.com/seatable/seatable-api-php/blob/main/README_File.md) - - [SysAdmin](https://github.com/seatable/seatable-api-php/blob/main/README_SysAdmin.md) - - [TeamAdmin](https://github.com/seatable/seatable-api-php/blob/main/README_TeamAdmin.md) - - [User](https://github.com/seatable/seatable-api-php/blob/main/README_User.md) diff --git a/docs/clients/python_api.md b/docs/clients/python_api.md deleted file mode 100644 index a2d1523f..00000000 --- a/docs/clients/python_api.md +++ /dev/null @@ -1,14 +0,0 @@ -# Python client - -The SeaTable Python Client encapsulates SeaTable Server Restful API. You can call it in your python program. - -!!! info "Unique Python library" - Unlike JavaScript, external python programs and python scripts, executed in SeaTable, use the same python library and therefore share the same functions. For an overview of the available functions, read the chapter of [script programming with Python](../scripts/python/introduction.md) in this documentation. - -## Installation - -``` -pip3 install seatable-api -``` - -The source code of the Python Client API is available at [GitHub](https://github.com/seatable/seatable-api-python). diff --git a/docs/index.md b/docs/index.md index 52af17e0..b4963203 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,76 +1,36 @@ -# Introduction +# SeaTable Developer Manual -You've decided to venture into developing your own script, plugin, or custom application: excellent choice! This guide is designed to cover all aspects of this journey. While some descriptions might seem obvious to seasoned professionals, this manual is crafted to assist novice developers who are just starting out. +This manual covers the programmatic interfaces to SeaTable: client libraries, the scripting API reference, and plugin development. ## Who is this manual for? -The Developer Manual caters to **developers** interested in utilizing custom scripts within SeaTable, creating their own plugins, or developing custom programs. Both minimal programming skills and knowledge of SeaTable are therefore recommended to take full advantage of this manual. +This manual is for **developers** who want to: -!!! info "Tips for beginners" +- Build external applications that communicate with SeaTable (Python, JavaScript, PHP) +- Develop custom plugins for SeaTable +- Look up the complete function reference of the SeaTable API libraries - You don't feel familiar enough with coding or with SeaTable? Depending on your actual skills, knowledge and aims, here are some suggested starting points: +!!! tip "Looking to write scripts inside SeaTable?" - - You would like to get started but currently have no programming knowledge? We invite you to consult the [Coding for beginners page](/introduction/coding_for_beginners) + If you want to create and run scripts directly within a SeaTable base, head to the [SeaTable User Manual](https://seatable.com/help/scripts/) for step-by-step examples and getting-started guides. The function reference for the scripting libraries is documented in this developer manual. - - You are new to SeaTable? Do not hesitate to consult [SeaTable's user manual](https://seatable.com/help/) to get more familiar with it. +## Languages -## Scope of this manual +| Language | Use case | +|---|---| +| **[Python](python/)** | External apps, data pipelines, automations, scripts in SeaTable | +| **[JavaScript](javascript/)** | Scripts in SeaTable, Node.js apps, frontend integrations | +| **[PHP](php/)** | Web applications, server-side integrations | +| **[Ruby](ruby/)** | Community client | -This guide illustrates **three fundamental approaches** to development within SeaTable: +!!! example "Other languages" -1. [Scripting within SeaTable](/scripts/): Create custom logic or perform individual data processing using JavaScript or Python, both supported within SeaTable. -2. [SeaTable plugins](/plugins/): Develop plugins capable of interacting with, visualizing, and operating on data within a SeaTable Base. -3. [Utilizing any programming language with SeaTable's API](/clients/): Seamlessly interact with the SeaTable API to construct your own web pages or programs. + For other languages, use the [REST API](https://api.seatable.com) directly. It provides interactive examples and code snippets. -!!! info "JavaScript or Python scripts?" +## Data Model - Differences between JavaScript and Python (in terms of abilities and requirements) are mentioned in the [Scripting introduction page](./scripts/index.md) to help you make the right choice depending on your needs +SeaTable organizes data in bases, tables, columns, rows, and views. The complete schema definition is available at [api.seatable.com/reference/models](https://api.seatable.com/reference/models). -All instructions provided are applicable to self-hosted SeaTable installations (Enterprise and Developer Editions), as well as to SeaTable Cloud. +## Plugin Development -### Where to start? - -For guidance on choosing the right section within this manual, refer to the decision tree diagram below. - -![Image title](/media/developer_decision_tree.png){ align=left } - -If you aim to integrate a software product with SeaTable, note that SeaTable supports multiple workflow automation tools such as [n8n](https://n8n.io/integrations/seatable/), [Zapier](https://zapier.com/apps/seatable/integrations), and [Make](https://www.make.com/en/integrations/seatable). Please refer to the [SeaTable User Manual](https://seatable.com/help/integrations/) for detailed information on these integrations, as they are not covered here. - -## Requirements - -### Development system - -To begin your development journey with SeaTable, you'll need a SeaTable system. If you're planning to create short scripts, [SeaTable Cloud](https://seatable.com/prices/) could be a suitable option. However, for more in-depth development or when creating plugins, it's highly recommended to set up your own SeaTable Server. Refer to the [Admin manual](https://admin.seatable.com) for installation instructions. - -!!! warning annotate "Known limitations of SeaTable Cloud" - - 1. **Custom Plugin Installation**: [SeaTable Cloud](https://cloud.seatable.io) does not support the installation of custom plugins. - 2. **Python Script Runs Limitation**: The number of Python script runs is constrained by your current SeaTable Cloud subscription. - - Therefore, it's recommended to set up your own SeaTable Server if you intend to develop custom plugins, applications, or run multiple Python scripts. For further information about deploying your server, please refer to the [Admin Manual](https://admin.seatable.com). - -### Authentication - -The actual authentication depends on the development approach one chooses. - -=== "Scripts" - - JavaScript scripts do not require any authentication at all because these scripts are executed in the browser of the user and the user has to be authenticated already. - - Python scripts require an authentication to get data from the base, but the `context` object contains everything for an easy authentication. - -=== "Plugins" - - Plugins interact with the data of one base. SeaTable provides all required functions for easy authentication. - -=== "Client APIs" - - If you want to build your own application you always have to authenticate with a base token against the base (learn more about the different tokens used by SeaTable in the [API Reference](https://api.seatable.com/reference/authentication)). - -## Data model - -{% - include-markdown "includes.md" - start="" - end="" -%} \ No newline at end of file +Custom plugins can visualize and interact with base data inside SeaTable. Plugin development requires JavaScript and React. See the [Plugin Development](plugins/) section for details. diff --git a/docs/introduction/coding_for_beginners.md b/docs/introduction/coding_for_beginners.md deleted file mode 100644 index 193f6997..00000000 --- a/docs/introduction/coding_for_beginners.md +++ /dev/null @@ -1,154 +0,0 @@ -# Coding for beginners - -## What to learn? - -The Developer Manual is divided into three major sections ([scripts](../scripts/index.md), [plugins](../plugins/index.md), or [API client](../clients/index.md)) depending on what you want to do with SeaTable. Your development requirements will naturally vary based on your intended project. Below is an outline of the skills you might need: - -=== "Scripts" - - Scripts inside SeaTable can only be written with either JavaScript or Python. Therefore you will only require one of these programming languages. - -=== "Plugins" - - The development of a custom plugin for your own SeaTable Server requires profound knowledge of JavaScript and React. - - Even if the `SeaTable plugin templates` offers some reusable components, you will need some experience with React to build the interface of your plugin. - -=== "Client API's" - - Due to the publicly available and well documented API documentation, you can theoretically interact with SeaTable using any programming language. - -## Learn the fundamentals - -If you're relatively new to development, diving into general tutorials can lay a strong foundation for your SeaTable development journey. - -While numerous free online tutorials cover various programming languages, investing in a comprehensive online course or a well-structured book can be invaluable. While free resources are available, a structured course or book often offers a more cohesive and thorough learning experience. - -These paid resources, though requiring a small investment, often provide: - -- **Structured Learning**: A step-by-step approach ensuring a coherent understanding. -- **Comprehensive Content**: In-depth coverage of essential concepts and practical applications. -- **Consistency**: Ensuring continuity and coherence in learning. - -Remember, while free tutorials are abundant, investing in a structured resource can significantly expedite your learning process and provide a solid understanding of programming fundamentals essential for SeaTable development. - -!!! info "These are personal recommendations" - - The following sources do not contain any affiliate links and we do not earn any money from these recommendations. These are just good sources that we have used ourselves in the past. - -=== "JavaScript" - - **Free online course** - - : A solid and __free online__ course is available from codecademy.com. The course [Learn JavaScript](https://www.codecademy.com/learn/introduction-to-javascript) requires a registration but is free and teaches you in approx. 20 hours all necessary skills. - - **Best online course** - - : The best __online course__ on javascript comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh manages to explain all the important basics for programming with JavaScript in his course [The Ultimate JavaScript Series](https://codewithmosh.com/p/ultimate-javascript-series). Once you have completed this course, you should be able to write your first scripts with ease. A monthly subscription costs just $29. - - **Book for Beginners** - - : If you prefer a __book__, then we can recommend [JavaScript from Beginner to Professional](https://www.amazon.de/JavaScript-Beginner-Professional-building-interactive/dp/1800562527/). It gives you all the basics for your first steps with JavaScript. - -=== "Python" - - **Free online course** - - : An easy to follow beginner guide comes from Google. At [https://developers.google.com/edu/python](https://developers.google.com/edu/python) you can find this well balanced course to learn how to do your first steps. - - **Best online course** - - : The best __online course__ on Python comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh manages to explain all the important basics for programming with Python in his course [Complete Python Mastery](https://codewithmosh.com/p/python-programming-course-beginners). Once you have completed this course, you should be able to write your first scripts with ease. A monthly subscription costs just $29. - - **Book for Beginners** - - : Our recommended book for beginners is called [Learn Python in One Day and Learn It Well](https://www.amazon.de/Python-2nd-Beginners-Hands-Project-ebook/dp/B071Z2Q6TQ) and as far as we can tell it keeps his promise. Most of our working students have read this book if they want to learn more about Python. - -=== "React" - - **Free online course** - - : This free online course comes to you from [Scrimba](https://scrimba.com/). Scrimba is a coding bootcamp with mainly paid courses and a high amount of interactive screencasts. The React course [Learn React](https://scrimba.com/learn/learnreact) is fortunately free of charge. - - **Best online course** - - : The best __online course__ on React comes from [Mosh Hamedani](https://codewithmosh.com/). Mosh will guide and teach you React until and will build a complete Video Game Discovery App. The course is called [React Course for Beginners](https://codewithmosh.com/p/ultimate-react-part1). - -## Learning by doing - -Some of us are more comfortable with learning by doing. The principle is simple: dissect a working example, understand it, and finally modify it so that it achieves what we want. - -Here are **three examples**, one for each approach described in this manual: - -### Python script to get the structure of a base - -You can take the following python code and copy&paste it to SeaTable. It will return the complete metastructure of your base. Easy or not? If you need some more information about this script, please refer to [this step-by-step presentation](https://seatable.com/help/python-beispiel-die-metastruktur-einer-base-auslesen/). - -=== "Python code" - - ```python - from seatable_api import Base, context - base = Base(context.api_token, context.server_url) - base.auth() - - metadata = base.get_metadata() - - print("--- COMPLETE BASE STRUCTURE WITH ALL BASES AND COLUMNS ---") - for table in metadata['tables']: - print('.') - print("Table: "+table['name']+" (ID: "+table['_id']+")") - for column in table['columns']: - link_target = "" - if column['type'] == "link": - link_target = " --> "+column['data']['other_table_id'] - if column['data']['other_table_id'] == table['_id']: - link_target = " --> "+column['data']['table_id'] - print(" --> "+column['name']+" ("+column['type']+link_target+")") - ``` - -=== "Output example" - - ``` - --- COMPLETE BASE STRUCTURE WITH ALL BASES AND COLUMNS --- - . - Table: Opportunities (ID: 9g8f) - --> Name (text) - --> Status (single-select) - --> Prio (single-select) - --> Owner (collaborator) - --> Customer (link --> deGa) - --> Estimated value (number) - --> Proposal deadline (date) - --> Contacts (link --> lYb8) - --> Interactions (link --> 0000) - . - Table: Interactions (ID: 0000) - --> Interaction ID (auto-number) - --> Opportunity (link --> 9g8f) - --> Type (single-select) - --> Interaction (formula) - --> Opportunity status (formula) - --> Date and time (date) - --> Contact (link --> lYb8) - --> Notes (long-text)# - ``` - -Feel free to check the other [JavaScript](../scripts/javascript/examples/index.md) and [Python](../scripts/python/examples/index.md) examples. - -### Existing plugins - -SeaTable provides some plugins to visualize your data, for example the [Gallery](https://seatable.com/help/anleitung-zum-galerie-plugin/), [Timeline](https://seatable.com/help/anleitung-zum-timeline-plugin/), [Kanban](https://seatable.com/help/anleitung-zum-kanban-plugin/) and so on, but it also offers everything you need to you build your own plugin. There are no limits to the imagination, it just requires some time and React skills. - -For each existing plugin, you can find a corresponding [Github repository](https://github.com/orgs/seatable/repositories?q=seatable-plugin) that will allow you to fork/clone the code and try by yourself (you will probably need some basic git skills too). Please note that this is probably the one of the three approaches ([scripts](../scripts/index.md), [plugins](../plugins/index.md), or [API client](../clients/index.md)) that **requires the most skills**. - -### Using SeaTable APIs: the SeaTable ideas custom app example - -There are multiple API classes available for various programming languages. This enables you to build any app or website you want. - -Our feature request tool [SeaTable Ideas](https://ideas.seatable.com) is an example for such a website. It uses SeaTable as database and the frontend is build completely with PHP and the [slim framework](https://www.slimframework.com/). - -![Screenshot of ideas.seatable.com](/media/ideas.png). - -Do not hesitate to consult this [pretty detailed article](https://seatable.com/seatable-app-frontend-php/) about the logic behind this app. - -Of course, the [SeaTable API Reference](https://api.seatable.com) is another good place to start as it allows you to experiment with most queries, see the responses, and get the corresponding source code for all supported languages. \ No newline at end of file diff --git a/docs/introduction/get_support.md b/docs/introduction/get_support.md index 0ce3e503..0b29e208 100644 --- a/docs/introduction/get_support.md +++ b/docs/introduction/get_support.md @@ -1,19 +1,13 @@ -# Get support +# Get Support -Next to this developer guide there are more documentations available. To learn more about the usage of SeaTable, the installation of your own server or the SeaTable API, please refer to their respective manuals: +Beyond this developer manual, SeaTable provides several other resources: -- [SeaTable User Manual](https://help.seatable.com) detailing how to use SeaTable (with a special section about [scripts](https://seatable.com/help/scripts/)) -- [SeaTable Admin Manual](https://admin.seatable.com) covering all relevant admin topics, from installation, configuration, upgrade, and maintenance -- [SeaTable API Reference](https://api.seatable.com) containing everything you need to know to use SeaTable's API +- [SeaTable User Manual](https://seatable.com/help/) covers how to use SeaTable, including a section about [scripts](https://seatable.com/help/scripts/) +- [SeaTable Admin Manual](https://admin.seatable.com) covers installation, configuration, upgrades, and maintenance +- [SeaTable API Reference](https://api.seatable.com) documents all available API endpoints with interactive examples -See the [official SeaTable channel](https://youtube.com/seatable) on YouTube for tutorials, guides and overviews. Visit [our blog](https://seatable.com/blog/) for latest news and to learn more about what is going on in and around SeaTable. +The [Community Forum](https://forum.seatable.com) is a good place to ask questions, share experiences, or report bugs. You can also find tutorials and guides on the [SeaTable YouTube channel](https://youtube.com/seatable) and the [SeaTable blog](https://seatable.com/blog/). -At any time you could have a look at the SeaTable [Community Forum](https://forum.seatable.com) to share your experience with other users or report issues or bugs. +!!! info "Enterprise Support" -!!! info "Enterprise support" - - If you're using SeaTable in your organization and need - assistance, e.g., to __digitalization of processes__, __develop custom solutions__ or __improve efficiency__, - [__get in touch__](mailto:sales@seatable.io) to discuss our __enterprise support__ offerings. - - We're happy to help! + If your organization needs help with digitalizing processes, developing custom solutions, or improving efficiency, [get in touch](mailto:sales@seatable.com) to discuss our enterprise support offerings. diff --git a/docs/introduction/index.md b/docs/introduction/index.md new file mode 100644 index 00000000..ceac3bbc --- /dev/null +++ b/docs/introduction/index.md @@ -0,0 +1,5 @@ +# Introduction + +Welcome to the SeaTable Developer Manual. This guide covers the programmatic interfaces to SeaTable. + +For an overview and where to start, see the [home page](../index.md). diff --git a/docs/javascript/columns.md b/docs/javascript/columns.md new file mode 100644 index 00000000..43f01541 --- /dev/null +++ b/docs/javascript/columns.md @@ -0,0 +1,226 @@ +# Columns + +{% + include-markdown "includes.md" + start="" + end="" +%} + +## Get Column(s) + +!!! abstract "getColumnByName" + + Get the column object of a table, specified by the column name. + + ``` js + base.getColumnByName(table, columnName); + ``` + + __Output__ Single column object (`undefined` if column doesn't exist) + + __Example__ + ``` js + const column = base.getColumnByName('Table1', 'Name'); + ``` + +!!! abstract "getColumns" + + Get all columns of a table. + + ``` js + base.getColumns(table); + ``` + + __Output__ Array of column objects + + __Example__ + ``` js + const columns = base.getColumns('Table1'); + columns.forEach((column) => { + console.log(column.name); + }); + ``` + +!!! abstract "listColumns" + + Get the columns of a table, optionally filtered by view. If no view is specified, all columns are returned. + + ``` js + base.listColumns(tableName, viewName); + ``` + + __Output__ Array of column objects + + __Example__ + ``` js + const columns = base.listColumns('Table1', 'Default View'); + ``` + +!!! abstract "getShownColumns :material-tag-outline:{ title='Scripting only' }" + + Get all visible columns of a table in a specific view (hidden columns are excluded). Only available in SeaTable scripts. + + ``` js + base.getShownColumns(table, view); + ``` + + __Output__ Array of column objects + + __Example__ + ``` js + const columns = base.getShownColumns('Table1', 'Default View'); + ``` + +!!! abstract "getColumnsByType" + + Get all columns of a specific type in a table. See the [API Reference](https://api.seatable.com/reference/models#supported-column-types) for supported column types. + + ``` js + base.getColumnsByType(table, type); + ``` + + __Output__ Array of column objects (empty array if no match) + + __Example__ + ``` js + const textColumns = base.getColumnsByType('Table1', 'text'); + ``` + +## Add Column + +!!! abstract "insertColumn" + + Add a new column to a table. + + ``` js + base.insertColumn(tableName, columnName, columnType, columnKey='', columnData=''); + ``` + + __Example__ + ``` js + import { ColumnTypes } from 'seatable-api'; + + await base.insertColumn('Table1', 'Notes', ColumnTypes.TEXT); + + // Insert after a specific column + await base.insertColumn('Table1', 'Notes', ColumnTypes.TEXT, '0000'); + + // Create a link column + await base.insertColumn('Table1', 'Link1', ColumnTypes.LINK, '', { + 'table': 'Table1', + 'other_table': 'Table2' + }); + ``` + +## Rename Column + +!!! abstract "renameColumn" + + Rename a column, identified by its column key. + + ``` js + base.renameColumn(tableName, columnKey, newColumnName); + ``` + + __Example__ + ``` js + await base.renameColumn('Table1', 'kSiR', 'New Name'); + ``` + +## Column Settings + +!!! abstract "resizeColumn" + + ``` js + base.resizeColumn(tableName, columnKey, newColumnWidth); + ``` + + __Example__ + ``` js + await base.resizeColumn('Table1', 'asFV', 500); + ``` + +!!! abstract "freezeColumn" + + ``` js + base.freezeColumn(tableName, columnKey, frozen); + ``` + + __Example__ + ``` js + await base.freezeColumn('Table1', '0000', true); + ``` + +!!! abstract "moveColumn" + + Move a column to the right of the target column. + + ``` js + base.moveColumn(tableName, columnKey, targetColumnKey); + ``` + + __Example__ + ``` js + await base.moveColumn('Table1', 'loPx', '0000'); + ``` + +!!! abstract "modifyColumnType" + + Change the type of an existing column. + + ``` js + base.modifyColumnType(tableName, columnKey, newColumnType); + ``` + + __Example__ + ``` js + import { ColumnTypes } from 'seatable-api'; + await base.modifyColumnType('Table1', 'nePI', ColumnTypes.NUMBER); + ``` + +!!! abstract "addColumnOptions" + + Add options to a single-select or multiple-select column. + + ``` js + base.addColumnOptions(tableName, columnName, options); + ``` + + __Example__ + ``` js + await base.addColumnOptions('Table1', 'Status', [ + {"name": "Done", "color": "#73d56e", "textColor": "#000000"}, + {"name": "Open", "color": "#ff8000", "textColor": "#ffffff"}, + ]); + ``` + +!!! abstract "addColumnCascadeSettings" + + Add cascade settings to a single-select column, limiting child options based on the parent column's selection. + + ``` js + base.addColumnCascadeSettings(tableName, childColumn, parentColumn, cascadeSettings); + ``` + + __Example__ + ``` js + await base.addColumnCascadeSettings('Table1', 'Sub-Category', 'Category', { + "Electronics": ["Phones", "Laptops"], + "Furniture": ["Chairs", "Tables"] + }); + ``` + +## Delete Column + +!!! abstract "deleteColumn" + + Delete a column, identified by its column key. + + ``` js + base.deleteColumn(tableName, columnKey); + ``` + + __Example__ + ``` js + await base.deleteColumn('Table1', 'bsKL'); + ``` diff --git a/docs/javascript/constants.md b/docs/javascript/constants.md new file mode 100644 index 00000000..1d1d3a3e --- /dev/null +++ b/docs/javascript/constants.md @@ -0,0 +1,31 @@ +# Constants + +When creating or modifying columns, use the `ColumnTypes` constants for type-safe column type references. + +``` js +import { ColumnTypes } from 'seatable-api'; +``` + +## ColumnTypes + +| Constant | Column type | +|---|---| +| `ColumnTypes.TEXT` | Text | +| `ColumnTypes.LONG_TEXT` | Long text | +| `ColumnTypes.NUMBER` | Number | +| `ColumnTypes.CHECKBOX` | Checkbox | +| `ColumnTypes.DATE` | Date & time | +| `ColumnTypes.SINGLE_SELECT` | Single select | +| `ColumnTypes.MULTIPLE_SELECT` | Multiple select | +| `ColumnTypes.IMAGE` | Image | +| `ColumnTypes.FILE` | File | +| `ColumnTypes.COLLABORATOR` | Collaborator | +| `ColumnTypes.LINK` | Link to other records | +| `ColumnTypes.FORMULA` | Formula | +| `ColumnTypes.CREATOR` | Creator | +| `ColumnTypes.CTIME` | Created time | +| `ColumnTypes.LAST_MODIFIER` | Last modifier | +| `ColumnTypes.MTIME` | Modified time | +| `ColumnTypes.GEOLOCATION` | Geolocation | +| `ColumnTypes.AUTO_NUMBER` | Auto number | +| `ColumnTypes.URL` | URL | diff --git a/docs/clients/javascript/examples/file-upload.md b/docs/javascript/files.md similarity index 57% rename from docs/clients/javascript/examples/file-upload.md rename to docs/javascript/files.md index a11790ba..f2441b9c 100644 --- a/docs/clients/javascript/examples/file-upload.md +++ b/docs/javascript/files.md @@ -1,19 +1,25 @@ -# File Upload +# Files -The following script uploads an image from the local filesystem and attaches it to an image column. +The `seatable-api` npm package does not currently support file or image uploads. To upload files, you need to use the SeaTable REST API directly via `fetch()`. -!!! note "seatable-api NPM Package" +## Upload workflow - The script does **not** use the `seatable-api` NPM package since the package does not currently support file/image uploads. - Instead, `fetch()` is used. The script does not require any external dependencies. +Uploading a file to SeaTable requires three steps: -## Prerequisites +1. **Get an upload link** from SeaTable +2. **Upload the file** to that link +3. **Attach the file** to a row by updating the file/image column -You need a valid API token in order to execute the script. -Set the `API_TOKEN` variable inside the script to the value of your token. -You can generate an API token inside the [SeaTable UI](https://seatable.com/help/create-api-tokens/) or by using your [account token](https://api.seatable.com/reference/createapitoken). +## Complete example: Upload an image -## Code +This Node.js script uploads an image from the local filesystem and attaches it to an image column in a new row. No external dependencies required. + +### Prerequisites + +- A valid API token ([how to generate one](https://seatable.com/help/create-api-tokens/)) +- Node.js installed on your machine + +### Code ```js import { readFileSync } from 'fs'; @@ -29,13 +35,11 @@ const FILE_PATH = 'Test.svg'; const FILE_NAME = basename(FILE_PATH); /** - * Get file upload link + * Step 1: Get upload link * Docs: https://api.seatable.com/reference/getuploadlink */ let url = `${SERVER_URL}/api/v2.1/dtable/app-upload-link/`; -console.log("Generating upload link...\n"); - let response = await fetch(url, { method: "GET", headers: { Authorization: `Token ${API_TOKEN}` }, @@ -43,10 +47,8 @@ let response = await fetch(url, { const uploadLink = await response.json(); -console.log(uploadLink, '\n'); - /** - * Upload file from the local filesystem + * Step 2: Upload file * Docs: https://api.seatable.com/reference/uploadfile */ const file = readFileSync(FILE_PATH); @@ -56,8 +58,6 @@ formData.append("parent_dir", uploadLink.parent_path); formData.append("file", new Blob([file.buffer]), FILE_NAME); formData.append('relative_path', uploadLink.img_relative_path); -console.log('Uploading file...\n') - response = await fetch(uploadLink.upload_link + "?ret-json=1", { method: "POST", body: formData, @@ -65,31 +65,18 @@ response = await fetch(uploadLink.upload_link + "?ret-json=1", { const files = await response.json(); -console.log(files, '\n'); - /** - * Generate base token by using an API token - * Base operations such as inserting or updating rows require a base token - * Docs: https://api.seatable.com/reference/getbasetokenwithapitoken + * Step 3: Attach file to a row + * Docs: https://api.seatable.com/reference/appendrows */ url = `${SERVER_URL}/api/v2.1/dtable/app-access-token/`; -console.log('Generating base token...\n'); - response = await fetch(url, { headers: { Authorization: `Token ${API_TOKEN}` } }); const baseToken = await response.json(); -console.log(baseToken, '\n'); - -/** - * Append row to base - * This attaches the image to an image column - * This API call requires a valid base token - * Docs: https://api.seatable.com/reference/appendrows - */ const workspaceId = baseToken.workspace_id; const baseUuid = baseToken.dtable_uuid; const relativeImageURL = `/workspace/${workspaceId}${uploadLink.parent_path}/${uploadLink.img_relative_path}/${files[0].name}`; @@ -98,7 +85,6 @@ const body = { table_name: TABLE_NAME, rows: [ { - // The values of image/file columns are arrays [IMAGE_COLUMN_NAME]: [relativeImageURL], }, ], @@ -106,8 +92,6 @@ const body = { url = `${SERVER_URL}/api-gateway/api/v2/dtables/${baseUuid}/rows/`; -console.log('Appending row...\n') - response = await fetch(url, { method: 'POST', headers: { @@ -118,13 +102,15 @@ response = await fetch(url, { body: JSON.stringify(body), }); -console.log(await response.json()) +console.log(await response.json()); ``` -## Executing the Script - -You can use the following command to execute the script on the commandline using Node.js: +### Run ```bash node upload-file.js ``` + +## Further reading + +The complete file and image API is documented at [api.seatable.com](https://api.seatable.com/reference/uploadfile). diff --git a/docs/javascript/index.md b/docs/javascript/index.md new file mode 100644 index 00000000..2892ab4a --- /dev/null +++ b/docs/javascript/index.md @@ -0,0 +1,49 @@ +# JavaScript + +SeaTable provides a JavaScript API that works in two contexts: inside SeaTable as a script, or externally via Node.js or a frontend application. The core methods (tables, views, columns, rows, links, SQL) are the same in both contexts. Features that are only available in one context are clearly marked on the respective pages. + +## Script vs. External Client + +| | Script in SeaTable | External client | +|---|---|---| +| Installation | None | `npm install seatable-api` | +| Authentication | Not needed (user is already logged in) | API token required | +| Execution | In the browser | Node.js or frontend app | +| `await` required | Only for `query()` and `getLinkedRecords()` | For all calls | +| Exclusive features | [Context, Output, Utilities, Filter/QuerySet](scripting-features.md) | [Constants](constants.md) | + +## Installation + +```shell +npm install seatable-api +``` + +Not needed for scripts inside SeaTable. + +## Authentication + +External programs need an API token for authentication. API tokens can be [generated in the SeaTable web interface](https://seatable.com/help/create-api-tokens/). Scripts inside SeaTable require no authentication. + +```js +import { Base } from "seatable-api"; + +const base = new Base({ + server: "https://cloud.seatable.io", + APIToken: "your-api-token", +}); +await base.auth(); +``` + +## Async Operations + +External API calls are asynchronous and return promises. Use `await` to wait for the result. + +In scripting context, most methods are synchronous. The exceptions are `query()` and `getLinkedRecords()`, which also require `await`. + +## API Limits + +JavaScript calls are subject to [rate](https://api.seatable.com/reference/limits#general-rate-limits) and [size](https://api.seatable.com/reference/limits#size-limits) limits. Use batch operations (`batchAppendRows`, `batchUpdateRows`, `batchDeleteRows`) whenever possible to reduce the number of API calls. + +!!! tip "Looking for examples?" + + Step-by-step JavaScript script examples are available in the [SeaTable User Manual](https://seatable.com/help/scripts/). diff --git a/docs/javascript/links.md b/docs/javascript/links.md new file mode 100644 index 00000000..b32649e0 --- /dev/null +++ b/docs/javascript/links.md @@ -0,0 +1,116 @@ +# Links + +Link columns connect rows between tables. Most link operations require the `link_id`, which you can retrieve with `getColumnLinkId`. + +## Get Link ID + +!!! abstract "getColumnLinkId" + + Get the link ID of a link column. You need this ID for all other link operations. + + ```js + base.getColumnLinkId(tableName, columnName); + ``` + + __Output__ Link ID string (e.g. `'aHL2'`) + + __Example__ + ```js + const linkId = base.getColumnLinkId('Table1', 'Related Records'); + ``` + +## Get Linked Records + +!!! abstract "getLinkedRecords" + + Get the linked records of one or more rows. Supports pagination per row. + + ```js + await base.getLinkedRecords(tableId, linkColumnKey, rows); + ``` + + __Output__ Object with row IDs as keys and arrays of linked record info as values. + + __Example__ + ```js + const linked = await base.getLinkedRecords('0000', '89o4', [ + {'row_id': 'FzNqJxVUT8KrRjewBkPp8Q', 'limit': 10, 'offset': 0}, + {'row_id': 'Jmnrkn6TQdyRg1KmOM4zZg', 'limit': 20} + ]); + + // Result: + // { + // 'FzNqJxVUT8KrRjewBkPp8Q': [ + // {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, + // ... + // ], + // 'Jmnrkn6TQdyRg1KmOM4zZg': [...] + // } + ``` + +## Add Link + +!!! abstract "addLink" + + Create a link between two rows in different tables. + + ```js + base.addLink(linkId, tableName, otherTableName, rowId, otherRowId); + ``` + + __Example__ + ```js + base.addLink('5WeC', 'Projects', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ'); + ``` + +## Update Link(s) + +!!! abstract "updateLink" + + Replace all linked records of a row with a new set. + + ``` js + base.updateLink(linkId, tableName, otherTableName, rowId, otherRowIds); + ``` + + __Example__ + ``` js + base.updateLink('r4IJ', 'Table1', 'Table2', 'BXhEm9ucTNu3FjupIk7Xug', [ + 'exkb56fAT66j8R0w6wD9Qg', + 'DjHjwmlRRB6WgU9uPnrWeA' + ]); + ``` + +!!! abstract "batchUpdateLinks" + + Update links for multiple rows at once. + + ``` js + base.batchUpdateLinks(linkId, tableName, otherTableName, rowIdList, otherRowsIdsMap); + ``` + + __Example__ + ``` js + await base.batchUpdateLinks('WaW5', 'Table1', 'Table2', + ['fRLglslWQYSGmkU7o6KyHw', 'eSQe9OpPQxih8A9zPXdMVA'], + { + 'fRLglslWQYSGmkU7o6KyHw': ['MdfUQiWcTL--uMlrGtqqgw', 'E7Sh3FboSPmfBlDsrj_Fhg'], + 'eSQe9OpPQxih8A9zPXdMVA': ['cWHbzQiTR8uHHzH_gVSKIg', 'X56gE7BrRF-i61YlE4oTcw'] + } + ); + ``` + +## Remove Link + +!!! abstract "removeLink" + + Remove a link between two rows. + + ``` js + base.removeLink(linkId, tableName, otherTableName, rowId, otherRowId); + ``` + + __Example__ + ``` js + base.removeLink('5WeC', 'Projects', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ'); + ``` diff --git a/docs/javascript/metadata.md b/docs/javascript/metadata.md new file mode 100644 index 00000000..fa15b218 --- /dev/null +++ b/docs/javascript/metadata.md @@ -0,0 +1,37 @@ +# Metadata + +!!! abstract "getMetadata" + + Get the complete structure of a base -- tables, views, and columns. Does not include row data. + + ``` js + base.getMetadata(); + ``` + + __Example output__ + ```json + { + "tables": [{ + "_id": "4krH", + "name": "Contact", + "is_header_locked": false, + "columns": [{ + "key": "0000", + "type": "text", + "name": "Name", + "editable": true, + "width": 200 + }], + "views": [{ + "_id": "0000", + "name": "Default view", + "type": "table", + "is_locked": false, + "filters": [], + "sorts": [], + "groupbys": [], + "hidden_columns": [] + }] + }] + } + ``` diff --git a/docs/javascript/rows.md b/docs/javascript/rows.md new file mode 100644 index 00000000..247e761e --- /dev/null +++ b/docs/javascript/rows.md @@ -0,0 +1,252 @@ +# Rows + +{% + include-markdown "includes.md" + start="" + end="" +%} + +## Get Row(s) + +!!! abstract "getRow" + + Get a single row by its ID. + + ``` js + base.getRow(table, rowId); + ``` + + __Output__ Single row object + + __Example__ + ``` js + const row = base.getRow('Table1', 'M_lSEOYYTeuKTaHCEOL7nw'); + ``` + +!!! abstract "getRows" + + Get all rows displayed in a view. + + ``` js + base.getRows(table, view); + ``` + + __Output__ Array of row objects + + __Example__ + ``` js + const rows = base.getRows('Table1', 'Default View'); + ``` + +!!! abstract "listRows" + + Get rows with optional sorting and pagination. Particularly useful for large tables. + + ``` js + base.listRows(tableName, viewName='', orderBy='', desc='', start='', limit=''); + ``` + + __Output__ Array of row objects + + __Example__ + ``` js + // Simple + const rows = await base.listRows('Table1'); + + // With pagination and sorting + const rows = await base.listRows('Table1', 'Default View', 'Name', true, 0, 100); + ``` + +!!! abstract "getGroupedRows :material-tag-outline:{ title='Scripting only' }" + + Get rows grouped according to the view's grouping settings. Only available in SeaTable scripts. + + ``` js + base.getGroupedRows(table, view); + ``` + + __Output__ Array of group objects, each containing a `rows` array + + __Example__ + ``` js + const table = base.getTableByName('Table1'); + const view = base.getViewByName(table, 'Grouped View'); + const groups = base.getGroupedRows(table, view); + ``` + +!!! abstract "query" + + Use SQL to query a base. Most SQL syntax is supported -- see the [SQL Reference](/sql/) for details. + + ``` js + await base.query(sql); + ``` + + !!! info "Backticks for special names" + Escape table or column names that contain spaces or special characters with backticks: `` SELECT * FROM `My Table` `` + + __Output__ Array of row objects + + __Example__ + ``` js + const data = await base.query('SELECT name, price FROM Bill WHERE year = 2021'); + ``` + + ``` js + // Aggregation + const data = await base.query('SELECT name, SUM(price) FROM Bill GROUP BY name'); + ``` + +!!! abstract "filter :material-tag-outline:{ title='Scripting only' }" + + Filter rows using a filter expression. Returns a QuerySet with chainable methods. Only available in SeaTable scripts. + + ``` js + base.filter(tableName, viewName, filterExpression); + ``` + + __Output__ QuerySet object + + __Example__ + ``` js + // Get all rows where status is "Done" + const querySet = base.filter('Table1', 'Default View', 'Status = "Done"'); + const rows = querySet.all(); + const count = querySet.count(); + const first = querySet.first(); + ``` + + QuerySet methods: `.all()`, `.count()`, `.first()`, `.last()`, `.get(filter)`, `.filter(filter)`, `.delete()`, `.update(rowData)` + +## Add Row(s) + +!!! abstract "appendRow" + + Append a new row to the end of a table. + + ``` js + base.appendRow(tableName, rowData, applyDefault=false); + ``` + + Set `applyDefault` to `true` to use default column values for unspecified columns. + + __Example__ + ``` js + base.appendRow('Table1', { + 'Name': 'New entry', + 'Status': 'Open' + }); + ``` + +!!! abstract "insertRow" + + Insert a row after a specific anchor row. + + ``` js + base.insertRow(tableName, rowData, anchorRowId, applyDefault=false); + ``` + + __Example__ + ``` js + await base.insertRow('Table1', {'Name': 'Inserted row'}, 'U_eTV7mDSmSd-K2P535Wzw'); + ``` + +!!! abstract "batchAppendRows" + + Append multiple rows at once. More efficient than calling `appendRow` in a loop. + + ``` js + base.batchAppendRows(tableName, rowsData, applyDefault=false); + ``` + + __Example__ + ``` js + await base.batchAppendRows('Table1', [ + {'Name': 'Row 1', 'Status': 'Open'}, + {'Name': 'Row 2', 'Status': 'Done'}, + {'Name': 'Row 3', 'Status': 'Open'} + ]); + ``` + +## Update Row(s) + +!!! abstract "updateRow" + + Update a single row identified by its row ID. + + ``` js + base.updateRow(tableName, rowId, rowData); + ``` + + In scripting context, you can also pass a row object instead of a row ID. + + __Example__ + ``` js + base.updateRow('Table1', 'U_eTV7mDSmSd-K2P535Wzw', { + 'Status': 'Done' + }); + ``` + +!!! abstract "modifyRows :material-tag-outline:{ title='Scripting only' }" + + Update multiple rows at once in scripting context. Pass two arrays: the rows to update and the corresponding update data. + + ``` js + base.modifyRows(table, rows, updatedRows); + ``` + + __Example__ + ``` js + const table = base.getTableByName('Table1'); + const rows = base.getRows(table, base.getViewByName(table, 'Default View')); + const selectedRows = rows.filter(row => row['Status'] === 'Open'); + const updates = selectedRows.map(() => ({'Status': 'Archived'})); + base.modifyRows(table, selectedRows, updates); + ``` + +!!! abstract "batchUpdateRows" + + Update multiple rows at once. Each entry specifies a row ID and the data to update. + + ``` js + base.batchUpdateRows(tableName, rowsData); + ``` + + __Example__ + ``` js + await base.batchUpdateRows('Table1', [ + {"row_id": "fMmCFyoxT4GN5Y2Powbl0Q", "row": {"Name": "Updated 1"}}, + {"row_id": "cF5JTE99Tae-VVx0BGT-3A", "row": {"Name": "Updated 2"}} + ]); + ``` + +## Delete Row(s) + +!!! abstract "deleteRow" + + Delete a single row by its ID. + + ``` js + base.deleteRow(tableName, rowId); + ``` + + __Example__ + ``` js + base.deleteRow('Table1', 'U_eTV7mDSmSd-K2P535Wzw'); + ``` + +!!! abstract "batchDeleteRows" + + Delete multiple rows at once. + + ``` js + base.batchDeleteRows(tableName, rowIds); + ``` + + __Example__ + ``` js + await base.batchDeleteRows('Table1', [ + 'fMmCFyoxT4GN5Y2Powbl0Q', + 'cF5JTE99Tae-VVx0BGT-3A' + ]); + ``` diff --git a/docs/javascript/scripting-features.md b/docs/javascript/scripting-features.md new file mode 100644 index 00000000..b9ab0eaf --- /dev/null +++ b/docs/javascript/scripting-features.md @@ -0,0 +1,108 @@ +# Scripting Features + +These features are only available when running JavaScript scripts inside SeaTable. They provide access to the browser context, output functions, and utility helpers. + +## Context + +The `base.context` object provides information about the current user interaction. + +!!! abstract "currentTable" + + The currently selected table name. + + ``` js + const tableName = base.context.currentTable; + ``` + +!!! abstract "currentRow" + + The current row when the script is triggered via a button. Contains the full row data. + + ``` js + const row = base.context.currentRow; + output.text(row['Name']); + ``` + + !!! warning + `currentRow` is only available when the script is executed via a [button column](https://seatable.com/help/skript-manuell-per-schaltflaeche-oder-automation-ausfuehren/). When running manually, `currentRow` is `undefined`. + +## Output + +The `output` object displays results in the script output panel. + +!!! abstract "output.text" + + Display text or any variable in the output panel. Accepts strings, numbers, objects, and arrays. + + ``` js + output.text(anything); + ``` + + __Example__ + ``` js + output.text('Hello World'); + output.text(42); + output.text(row); + ``` + +!!! abstract "output.markdown" + + Display markdown-formatted content in the output panel. + + ``` js + output.markdown(markdownString); + ``` + + __Example__ + ``` js + output.markdown('# Title\n\nSome **bold** text.'); + ``` + +## Utilities + +The `base.utils` object provides helper functions. + +!!! abstract "formatDate" + + Format a date object to `YYYY-MM-DD`. + + ``` js + base.utils.formatDate(date); + ``` + + __Example__ + ``` js + const today = base.utils.formatDate(new Date()); + // Returns: "2026-03-18" + ``` + +!!! abstract "formatDateWithMinutes" + + Format a date object to `YYYY-MM-DD HH:mm`. + + ``` js + base.utils.formatDateWithMinutes(date); + ``` + + __Example__ + ``` js + const now = base.utils.formatDateWithMinutes(new Date()); + // Returns: "2026-03-18 14:30" + ``` + +!!! abstract "lookupAndCopy" + + Look up a value in another table and copy it. Similar to VLOOKUP in spreadsheets. + + ``` js + base.utils.lookupAndCopy(targetTable, targetColumn, targetColumnToSearch, sourceTable, sourceColumn, sourceColumnToSearch); + ``` + + __Example__ + ``` js + // Copy "Email" from Contacts table where the Name matches + base.utils.lookupAndCopy( + 'Orders', 'Customer Email', 'Customer Name', + 'Contacts', 'Email', 'Name' + ); + ``` diff --git a/docs/javascript/sql.md b/docs/javascript/sql.md new file mode 100644 index 00000000..ebd89701 --- /dev/null +++ b/docs/javascript/sql.md @@ -0,0 +1,40 @@ +# SQL Queries + +Use SQL to query a base. This is the most powerful way to access data. For the full SQL syntax reference, see the [SQL Reference](/sql/). + +!!! abstract "query" + + ``` js + await base.query(sql); + ``` + + !!! info "Backticks for special names" + Escape table or column names that contain spaces, special characters, or are [SQL function names](/sql/functions/) with backticks: `` SELECT * FROM `My Table` `` + + __Output__ Array of row objects + +__Example: SELECT all__ +``` js +const data = await base.query('SELECT * FROM Bill'); +``` + +__Example: WHERE__ +``` js +const data = await base.query('SELECT name, price FROM Bill WHERE year = 2021'); +``` + +__Example: ORDER BY__ +``` js +const data = await base.query('SELECT name, price, year FROM Bill ORDER BY year'); +``` + +__Example: GROUP BY__ +``` js +const data = await base.query('SELECT name, SUM(price) FROM Bill GROUP BY name'); +// Returns: [{'SUM(price)': 600, 'name': 'Bob'}, ...] +``` + +__Example: DISTINCT__ +``` js +const data = await base.query('SELECT DISTINCT name FROM Bill'); +``` diff --git a/docs/javascript/tables.md b/docs/javascript/tables.md new file mode 100644 index 00000000..e9bce2a0 --- /dev/null +++ b/docs/javascript/tables.md @@ -0,0 +1,100 @@ +# Tables + +{% + include-markdown "includes.md" + start="" + end="" +%} + +## Get Table(s) + +!!! abstract "getActiveTable :material-tag-outline:{ title='Scripting only' }" + + Get the currently selected table. Only available in SeaTable scripts. + + ``` js + base.getActiveTable(); + ``` + __Output__ Single table object + + __Example__ + ``` js + const table = base.getActiveTable(); + output.text(`The name of the active table is: ${table.name}`); + ``` + +!!! abstract "getTables" + + Get all tables of the current base. + + ``` js + base.getTables(); + ``` + __Output__ Array of table objects + + __Example__ + ``` js + const tables = base.getTables(); + ``` + +!!! abstract "getTableByName" + + Get a table object by its name. + + ``` js + base.getTableByName(tableName); + ``` + + __Output__ Single table object (`undefined` if table doesn't exist) + + __Example__ + ```js + const table = base.getTableByName('Table1'); + ``` + +## Add Table + +!!! abstract "addTable" + + Add a new table to this base. Ensure the name doesn't already exist. + + ``` js + base.addTable(tableName, lang='en', columns=[]); + ``` + + The `lang` and `columns` parameters are optional. + + __Example__ + ``` js + base.addTable('New table'); + ``` + +## Rename Table + +!!! abstract "renameTable" + + Rename an existing table. + + ``` js + base.renameTable(oldName, newName); + ``` + + __Example__ + ``` js + base.renameTable('Table1', 'Projects 2023'); + ``` + +## Delete Table + +!!! abstract "deleteTable" + + Delete a table from the base. The table can be [restored from the logs](https://seatable.com/help/eine-geloeschte-tabelle-wiederherstellen/). Deleting the last table is not possible. + + ``` js + base.deleteTable(tableName); + ``` + + __Example__ + ``` js + base.deleteTable('Old table'); + ``` diff --git a/docs/javascript/views.md b/docs/javascript/views.md new file mode 100644 index 00000000..5855b58c --- /dev/null +++ b/docs/javascript/views.md @@ -0,0 +1,100 @@ +# Views + +{% + include-markdown "includes.md" + start="" + end="" +%} + +## Get View(s) + +!!! abstract "getActiveView :material-tag-outline:{ title='Scripting only' }" + + Get the current view of the active table. Only available in SeaTable scripts. + + ``` js + base.getActiveView(); + ``` + + __Output__ Single view object + + __Example__ + ``` js + const view = base.getActiveView(); + output.text(view.name); + ``` + +!!! abstract "getViewByName" + + Get a view of a table, specified by its name. + + ``` js + base.getViewByName(table, viewName); + ``` + + __Output__ Single view object (`undefined` if no view with that name exists) + + __Example__ + ``` js + const view = base.getViewByName('Table1', 'Default View'); + ``` + +!!! abstract "listViews" + + Get all the views of a table. + + ``` js + base.listViews(table); + ``` + + __Output__ Array of view objects + + __Example__ + ``` js + const views = base.listViews('Table1'); + ``` + +## Add View + +!!! abstract "addView" + + Add a new view to a table. + + ``` js + base.addView(table, viewName); + ``` + + __Example__ + ``` js + base.addView('Table1', 'My View'); + ``` + +## Rename View + +!!! abstract "renameView" + + Rename an existing view. + + ``` js + base.renameView(table, currentViewName, newViewName); + ``` + + __Example__ + ``` js + base.renameView('Table1', 'Default View', 'Main View'); + ``` + +## Delete View + +!!! abstract "deleteView" + + Delete a view. Deleting the last view is not possible. + + ``` js + base.deleteView(table, viewName); + ``` + + __Example__ + ``` js + base.deleteView('Table1', 'Old View'); + ``` diff --git a/docs/php/api-reference.md b/docs/php/api-reference.md new file mode 100644 index 00000000..eff05bb6 --- /dev/null +++ b/docs/php/api-reference.md @@ -0,0 +1,20 @@ +# API Reference + +The PHP client is auto-generated from the SeaTable OpenAPI specification. Detailed documentation for all endpoints, including parameters and response types, is available on GitHub: + +## Endpoint categories + +| Category | Description | Documentation | +|---|---|---| +| **Auth** | Obtain base tokens and account tokens | [Auth API](https://github.com/seatable/seatable-api-php/blob/main/README_Auth.md) | +| **Base** | Rows, columns, views, links, metadata, SQL queries | [Base API](https://github.com/seatable/seatable-api-php/blob/main/README_Base.md) | +| **File** | Upload and download files and images | [File API](https://github.com/seatable/seatable-api-php/blob/main/README_File.md) | +| **User** | Account info, bases, shared views, API tokens | [User API](https://github.com/seatable/seatable-api-php/blob/main/README_User.md) | +| **Team Admin** | Team management, members, sharing | [TeamAdmin API](https://github.com/seatable/seatable-api-php/blob/main/README_TeamAdmin.md) | +| **Sys Admin** | Server administration (self-hosted only) | [SysAdmin API](https://github.com/seatable/seatable-api-php/blob/main/README_SysAdmin.md) | + +## Additional resources + +- [Source code on GitHub](https://github.com/seatable/seatable-api-php) +- [Package on Packagist](https://packagist.org/packages/seatable/seatable-api-php) +- [Interactive API Reference](https://api.seatable.com) (language-agnostic, with code examples) diff --git a/docs/php/examples.md b/docs/php/examples.md new file mode 100644 index 00000000..1e09913a --- /dev/null +++ b/docs/php/examples.md @@ -0,0 +1,154 @@ +# Examples + +## Get account information + +Connect to SeaTable Cloud and retrieve your account details. + +```php +setAccessToken('YOUR_ACCOUNT_TOKEN'); + +$apiInstance = new SeaTable\Client\User\UserApi( + new GuzzleHttp\Client(), $config +); + +try { + $result = $apiInstance->getAccountInfo(); + print_r($result); +} catch (Exception $e) { + echo 'Exception: ', $e->getMessage(), PHP_EOL; +} +``` + +## List your bases + +```php +setAccessToken('YOUR_ACCOUNT_TOKEN'); + +$apiInstance = new SeaTable\Client\User\BasesApi( + new GuzzleHttp\Client(), $config +); + +try { + $result = $apiInstance->listBases(); + print_r($result); +} catch (Exception $e) { + echo 'Exception: ', $e->getMessage(), PHP_EOL; +} +``` + +## Get base metadata + +First obtain a Base Token from your API Token, then retrieve the metadata. + +```php +setAccessToken('YOUR_API_TOKEN'); + +$apiInstance = new SeaTable\Client\Auth\BaseTokenApi( + new GuzzleHttp\Client(), $config +); + +$result = $apiInstance->getBaseTokenWithApiToken(); + +// Step 2: Get Metadata +$config = SeaTable\Client\Configuration::getDefaultConfiguration() + ->setAccessToken($result['access_token']); + +$apiInstance = new SeaTable\Client\Base\BaseInfoApi( + new GuzzleHttp\Client(), $config +); + +try { + $result = $apiInstance->getMetadata($result['dtable_uuid']); + print_r($result); +} catch (Exception $e) { + echo 'Exception: ', $e->getMessage(), PHP_EOL; +} +``` + +## Execute an SQL query + +```php +setAccessToken('YOUR_API_TOKEN'); + +$authApi = new SeaTable\Client\Auth\BaseTokenApi( + new GuzzleHttp\Client(), $config +); +$auth = $authApi->getBaseTokenWithApiToken(); + +// Step 2: Query +$config = SeaTable\Client\Configuration::getDefaultConfiguration() + ->setAccessToken($auth['access_token']); + +$apiInstance = new SeaTable\Client\Base\RowsApi( + new GuzzleHttp\Client(), $config +); + +$sqlQuery = new SeaTable\Client\Base\SqlQuery([ + "sql" => "SELECT * FROM Table1", + "convert_keys" => false +]); + +try { + $result = $apiInstance->querySQL($auth['dtable_uuid'], $sqlQuery); + print_r($result); +} catch (Exception $e) { + echo 'Exception: ', $e->getMessage(), PHP_EOL; +} +``` + +## Add a row + +```php +setAccessToken('YOUR_API_TOKEN'); + +$authApi = new SeaTable\Client\Auth\BaseTokenApi( + new GuzzleHttp\Client(), $config +); +$auth = $authApi->getBaseTokenWithApiToken(); + +// Step 2: Append row +$config = SeaTable\Client\Configuration::getDefaultConfiguration() + ->setAccessToken($auth['access_token']); + +$apiInstance = new SeaTable\Client\Base\RowsApi( + new GuzzleHttp\Client(), $config +); + +$request = new SeaTable\Client\Base\AppendRows([ + 'table_name' => 'Table1', + 'rows' => [ + ['Name' => 'Inserted via API'], + ], + 'apply_default' => false, +]); + +try { + $result = $apiInstance->appendRows($auth['dtable_uuid'], $request); + print_r($result); +} catch (Exception $e) { + echo 'Exception: ', $e->getMessage(), PHP_EOL; +} +``` diff --git a/docs/php/index.md b/docs/php/index.md new file mode 100644 index 00000000..4782a56e --- /dev/null +++ b/docs/php/index.md @@ -0,0 +1,47 @@ +# PHP + +The SeaTable PHP Client encapsulates the SeaTable REST API. It is auto-generated from the public [OpenAPI specification](https://api.seatable.com), which ensures all API endpoints are covered automatically. + +- Source code on [GitHub](https://github.com/seatable/seatable-api-php) +- Package on [Packagist](https://packagist.org/packages/seatable/seatable-api-php) +- Complete endpoint documentation in the [API Reference](api-reference.md) + +## Installation + +``` +composer require seatable/seatable-api-php +``` + +## Authentication + +Most operations on base data require a **Base Token**. You obtain it by exchanging an **API Token**, which can be [generated in the SeaTable web interface](https://seatable.com/help/create-api-tokens/): + +```php +setAccessToken('YOUR_API_TOKEN'); + +$apiInstance = new SeaTable\Client\Auth\BaseTokenApi( + new GuzzleHttp\Client(), $config +); + +$result = $apiInstance->getBaseTokenWithApiToken(); +$baseToken = $result['access_token']; +$baseUuid = $result['dtable_uuid']; +``` + +??? question "Account-level operations" + + For operations like listing bases or getting user info, authenticate with an **Account Token** instead of an API Token. An Account Token identifies a user and gives access to all their bases. See the [API docs](https://api.seatable.com/reference/getaccounttokenfromusername) for how to obtain one. + +??? question "Connecting to a self-hosted server" + + By default, the client connects to SeaTable Cloud. For self-hosted installations, set the host: + + ```php + $config = SeaTable\Client\Configuration::getDefaultConfiguration(); + $config->setAccessToken('YOUR_TOKEN'); + $config->setHost('https://seatable.example.com'); + ``` diff --git a/docs/python/index.md b/docs/python/index.md new file mode 100644 index 00000000..afea71a7 --- /dev/null +++ b/docs/python/index.md @@ -0,0 +1,98 @@ +# Python + +Python scripts connect to SeaTable bases with the library [seatable-api](https://pypi.org/project/seatable-api/). The source code is available on [GitHub](https://github.com/seatable/seatable-api-python). + +The same library is used both **inside SeaTable scripts** and in **external Python programs**. The only difference is how you authenticate. All objects and methods work identically in both contexts. + +## Installation + +``` +pip install seatable-api +``` + +!!! info "Not needed for scripts inside SeaTable" + + When running scripts directly in SeaTable (via the built-in Python editor), `seatable-api` is already available. No installation required. + +## Script vs. External Client + +| | Script in SeaTable | External client | +|---|---|---| +| Authentication | `context.api_token` (automatic) | API token or account credentials (manual) | +| `context` object | Available | Not available | +| `current_row` (button execution) | Available | Not available | +| Python version | 3.12 | Your choice | +| Available libraries | [Predefined set](https://seatable.com/help/supported-python-libraries/) | Unlimited | +| Execution | SeaTable server (Python Pipeline) | Your own machine/server | + +## Authentication + +### In a SeaTable script + +Within SeaTable's integrated Python editor, the `context` object provides the authentication credentials automatically: + +```python +from seatable_api import Base, context +base = Base(context.api_token, context.server_url) +base.auth() +``` + +### In an external program + +When running Python on your own machine or server, you provide the API token and server URL directly. API tokens can be [generated in the SeaTable web interface](https://seatable.com/help/create-api-tokens/). + +```python +from seatable_api import Base + +API_TOKEN = 'your-api-token' # (1)! +SERVER_URL = 'https://cloud.seatable.io' + +base = Base(API_TOKEN, SERVER_URL) +base.auth() +``` + +1. Avoid exposing credentials directly in the code. Use environment variables or `.env` files instead. + +??? question "Authentication with account credentials" + + Instead of using an API token (which is specific to a base), you can authenticate with your SeaTable account credentials. This gives you access to all your bases. + + ```python + from seatable_api import Account + account = Account(username, password, server_url) + account.auth() + base = account.get_base(workspace_id, base_name) + ``` + + To find the `workspace_id`, open the base in your browser. The URL looks like `https://cloud.seatable.io/workspace/84254/dtable/MyBase`. + +??? question "Handling authorization expiration" + + For long-running programs, authorization may expire. Catch `AuthExpiredError` to re-authenticate: + + ```python + from seatable_api import Base, context + from seatable_api.exception import AuthExpiredError + + base = Base(context.api_token, context.server_url) + base.auth() + + while True: + try: + base.append_row('Table1', {"xxx": "xxx"}) + ... + except AuthExpiredError: + base.auth() + ``` + +## Base Operation Limits + +Python scripts rely on the [SeaTable API](https://api.seatable.com) and are subject to [rate](https://api.seatable.com/reference/limits#general-rate-limits) and [size](https://api.seatable.com/reference/limits#size-limits) limits. Tips to stay within limits: + +- Be careful with operations in `for` or `while` loops +- Use **batch operations** whenever possible: + - `base.batch_append_rows` + - `base.batch_update_rows` + - `base.batch_delete_rows` + - `base.batch_update_links` +- Learn more about [optimizing your API calls](https://seatable.com/api-optimization/) diff --git a/docs/scripts/python/objects/accounts.md b/docs/python/objects/accounts.md similarity index 99% rename from docs/scripts/python/objects/accounts.md rename to docs/python/objects/accounts.md index 80d9f984..e8b6a743 100644 --- a/docs/scripts/python/objects/accounts.md +++ b/docs/python/objects/accounts.md @@ -256,3 +256,4 @@ The account object provides an interface to list workspaces, add/copy/delete bas base_metadata = account.copy_base(35, 'My Base', 74) print(base_metadata) ``` + diff --git a/docs/scripts/python/objects/columns.md b/docs/python/objects/columns.md similarity index 100% rename from docs/scripts/python/objects/columns.md rename to docs/python/objects/columns.md diff --git a/docs/scripts/python/objects/communication-utils.md b/docs/python/objects/communication-utils.md similarity index 92% rename from docs/scripts/python/objects/communication-utils.md rename to docs/python/objects/communication-utils.md index 18173c2d..b302a48c 100644 --- a/docs/scripts/python/objects/communication-utils.md +++ b/docs/python/objects/communication-utils.md @@ -6,6 +6,29 @@ Several outgoing communications features are available within SeaTable. Whether !!! info "Going further" Keep in mind that communication methods will probably require other coding skills as they mostly make sense outside of SeaTable. The [API Reference](https://api.seatable.com/reference/getbaseactivitylog-1) also details other methods such as getting base or row activities logs, which might also help you stay informed about what's happening in the base (but without the automatic firing on the SeaTable side of the methods presented here). +## Send email + +!!! abstract "send_email" + + Send an email using a pre-configured email account in SeaTable. The email account must be set up in the system administration. + + ``` python + base.send_email(account_name, msg, **kwargs) + ``` + + - `account_name`: name of the configured email account + - `msg`: email message content + + __Example__ + + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + base.send_email('my-email-account', 'Hello from SeaTable!') + ``` + ## Toast notifications !!! abstract "send_toast_notification" diff --git a/docs/scripts/python/objects/context.md b/docs/python/objects/context.md similarity index 83% rename from docs/scripts/python/objects/context.md rename to docs/python/objects/context.md index 40c78a6b..5ba71faf 100644 --- a/docs/scripts/python/objects/context.md +++ b/docs/python/objects/context.md @@ -115,3 +115,21 @@ When the script is running in the cloud, the context object provides a context e from seatable_api import context print(context.current_id_in_org) ``` + +!!! abstract "get_setting_by_key" + + Get a context setting by its key. This provides access to additional context data beyond the predefined properties above. + + ``` python + context.get_setting_by_key(key) + ``` + + __Output__ The value of the setting, or `None` if not found + + __Example__ + + ``` python + from seatable_api import context + value = context.get_setting_by_key('server_url') + print(value) + ``` diff --git a/docs/scripts/python/objects/date-utils.md b/docs/python/objects/date-utils.md similarity index 100% rename from docs/scripts/python/objects/date-utils.md rename to docs/python/objects/date-utils.md diff --git a/docs/scripts/python/objects/files.md b/docs/python/objects/files.md similarity index 100% rename from docs/scripts/python/objects/files.md rename to docs/python/objects/files.md diff --git a/docs/scripts/python/objects/links.md b/docs/python/objects/links.md similarity index 79% rename from docs/scripts/python/objects/links.md rename to docs/python/objects/links.md index bc915dab..a7200edb 100644 --- a/docs/scripts/python/objects/links.md +++ b/docs/python/objects/links.md @@ -1,8 +1,8 @@ # Links -!!! warning "llink id and column key" +!!! warning "link id and column key" - `link_id` should not be mistaken with the column `key`! The `key` value is unique (like an id) whereas the llink id will be shared between the two linked columns. Please note that `link_id` is used as argument to add/update/remove links, whereas you'll have to provide `link_column_key` (the link column `key`) to get linked records. Both information are available in the column object: + `link_id` should not be mistaken with the column `key`! The `key` value is unique (like an id) whereas the link id will be shared between the two linked columns. Please note that `link_id` is used as argument to add/update/remove links, whereas you'll have to provide `link_column_key` (the link column `key`) to get linked records. Both information are available in the column object: ```json { @@ -39,17 +39,17 @@ 1. The column `key` (referred as `link_column_key` in `base.get_linked_records` arguments) - 2. The llink id of the column (referred as `link_id` in the add/update/remove links operations) + 2. The link id of the column (referred as `link_id` in the add/update/remove links operations) 3. The table whose id is `table_id` is referred later in this section as the *source* table (the table containing this column) 4. The table whose id is `other_table_id` is referred later in this section as the *target* table -## Get llink id +## Get link id !!! abstract "get_column_link_id" - Get the llink id of the column `column_name` from the table `table_name`. + Get the link id of the column `column_name` from the table `table_name`. ``` python base.get_column_link_id(table_name, column_name) @@ -76,7 +76,7 @@ - Rows are from the *source* table (the table whose id is `table_id`) - - Records are the rows from the *target* table (the table linked to the *source* table in the column whose `key` is `link_column_key` or whose llink id is `link_id`) + - Records are the rows from the *target* table (the table linked to the *source* table in the column whose `key` is `link_column_key` or whose link id is `link_id`) !!! abstract "get_linked_records" @@ -88,7 +88,7 @@ 1. `table_id`: the id of *source* table - `link_column_key`: the column **key** of the link-type column of *source* table (**not** the llink id from `base.get_column_link_id`) + `link_column_key`: the column **key** of the link-type column of *source* table (**not** the link id from `base.get_column_link_id`) `rows`: a list of dicts, each of them containing: @@ -146,7 +146,7 @@ !!! abstract "add_link" - Add link in a link-type column. You'll need the *source* table's name `table_name`, the *target* table's name `other_table_name`, the llink id from the link-type column and both the ids of the rows you want to link: `row_id` for the row from the *source* table and `other_row_id` for the record from the *target* table. + Add link in a link-type column. You'll need the *source* table's name `table_name`, the *target* table's name `other_table_name`, the link id from the link-type column and both the ids of the rows you want to link: `row_id` for the row from the *source* table and `other_row_id` for the record from the *target* table. ``` python base.add_link(link_id, table_name, other_table_name, row_id, other_row_id) @@ -177,13 +177,37 @@ base.add_link(lin_id, TABLE1_NAME, TABLE2_NAME, current_row_id, 'J5St2clyTMu_OFf9WD8PbA') ``` - 1. Remember you can use `base.get_column_link_id` to get the llink id of a specific link-type column. + 1. Remember you can use `base.get_column_link_id` to get the link id of a specific link-type column. + +!!! abstract "batch_add_links" + + Add links for multiple rows at once. The `other_rows_ids_map` maps source row IDs to lists of target row IDs. + + ``` python + base.batch_add_links(link_id, table_name, other_table_name, other_rows_ids_map) + ``` + + __Output__ Dict containing the result of the operation + + __Example__ + + ``` python + base.batch_add_links( + 'r4IJ', + 'Table1', + 'Table2', + { + 'fRLglslWQYSGmkU7o6KyHw': ['OcCE8aX8T7a4dvJr-qNh3g', 'JckTyhN0TeS8yvH8D3EN7g'], + 'eSQe9OpPQxih8A9zPXdMVA': ['cWHbzQiTR8uHHzH_gVSKIg'] + } + ) + ``` ## Update link(s) !!! abstract "update_link" - Update the content of the link-type column whose llink id is `link_id` for the row with id `row_id` in the table `table_name`. It will remove all existing row links and add new links to records of table `other_table_name` with ids in the `other_rows_ids` list. + Update the content of the link-type column whose link id is `link_id` for the row with id `row_id` in the table `table_name`. It will remove all existing row links and add new links to records of table `other_table_name` with ids in the `other_rows_ids` list. ``` python base.update_link(link_id, table_name, other_table_name, row_id, other_rows_ids) @@ -212,7 +236,7 @@ !!! abstract "batch_update_links" - Same than above, except that it allows you to batch update infos of link-type columns for several rows at once. Learn more about `other_rows_ids_map` in the [SeaTable API Reference](https://api.seatable.com/reference/createrowlink). This function can't operate more than 1000 rows at once. If you need to deal with more than 1000 rows at once, please refer to the [common questions](../common_questions.md#dealing-with-more-than-1000-rows-at-once-with-batch-operations). + Same than above, except that it allows you to batch update infos of link-type columns for several rows at once. Learn more about `other_rows_ids_map` in the [SeaTable API Reference](https://api.seatable.com/reference/createrowlink). This function can't operate more than 1000 rows at once. To handle more than 1000 rows, use a loop with offset. ``` python base.batch_update_links(link_id, table_name, other_table_name, row_id_list, other_rows_ids_map) # (1)! @@ -272,3 +296,27 @@ base.auth() base.remove_link('5WeC', 'Table1', 'Table2', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ') ``` + +!!! abstract "batch_remove_links" + + Remove links for multiple rows at once. The `other_rows_ids_map` maps source row IDs to lists of target row IDs to unlink. + + ``` python + base.batch_remove_links(link_id, table_name, other_table_name, other_rows_ids_map) + ``` + + __Output__ Dict containing the result of the operation + + __Example__ + + ``` python + base.batch_remove_links( + 'r4IJ', + 'Table1', + 'Table2', + { + 'fRLglslWQYSGmkU7o6KyHw': ['OcCE8aX8T7a4dvJr-qNh3g'], + 'eSQe9OpPQxih8A9zPXdMVA': ['cWHbzQiTR8uHHzH_gVSKIg', 'X56gE7BrRF-i61YlE4oTcw'] + } + ) + ``` diff --git a/docs/scripts/python/objects/metadata.md b/docs/python/objects/metadata.md similarity index 91% rename from docs/scripts/python/objects/metadata.md rename to docs/python/objects/metadata.md index 62736fa2..0040bf5f 100644 --- a/docs/scripts/python/objects/metadata.md +++ b/docs/python/objects/metadata.md @@ -79,4 +79,4 @@ Metadata delivers the complete structure of a base with tables, views and column !!! info "Displaying long and complex objects" - If you have hard time reading the output of a function returning a long or complex object, please see [how to make a pretty print](../common_questions.md#printing-complex-elements-is-sometimes-difficult-to-read). + If you have a hard time reading the output of a complex object, use `json.dumps(result, indent=2)` for pretty printing. diff --git a/docs/scripts/python/objects/rows.md b/docs/python/objects/rows.md similarity index 86% rename from docs/scripts/python/objects/rows.md rename to docs/python/objects/rows.md index 72975629..2f10ea04 100644 --- a/docs/scripts/python/objects/rows.md +++ b/docs/python/objects/rows.md @@ -62,7 +62,7 @@ You'll find below all the available methods to interact with the rows of a SeaTa !!! abstract "query" - Use SQL to query a base. SQL queries are the most powerful way access data stored in a base. If you're not familiar with SQL syntax, we recommend using first the [SQL query plugin](https://seatable.com/help/anleitung-zum-sql-abfrage-plugin/). Most SQL syntax is supported, you can check the [SQL Reference](/scripts/sql/introduction.md) section of this manual for more information. + Use SQL to query a base. SQL queries are the most powerful way access data stored in a base. If you're not familiar with SQL syntax, we recommend using first the [SQL query plugin](https://seatable.com/help/anleitung-zum-sql-abfrage-plugin/). Most SQL syntax is supported, you can check the [SQL Reference](/sql/) section of this manual for more information. ``` python base.query(sql_statement) @@ -73,9 +73,7 @@ You'll find below all the available methods to interact with the rows of a SeaTa !!! info "Backticks for table or column names containing or special characters or using reserved words" For SQL queries, you can use numbers, special characters or spaces in the names of your tables and columns. However, you'll **have to** escape these names with backticks in order for your query to be correctly interpreted, for example `` SELECT * FROM `My Table` ``. - Similarly, if some of your of table or column names are the same as [SQL function](/scripts/sql/functions.md) names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. - - Similarly, if some of your of table or column names are the same as SQL function names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. + Similarly, if some of your table or column names are the same as [SQL function](/sql/functions/) names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. __Output__ List of row dicts (eventually empty if no row match the request's conditions) @@ -350,7 +348,7 @@ You'll find below all the available methods to interact with the rows of a SeaTa !!! abstract "batch_append_rows" - Append multiple rows to the table `table_name` at once. This function can't operate more than 1000 rows at once. If you need to deal with more than 1000 rows at once, please refer to the [common questions](../common_questions.md#dealing-with-more-than-1000-rows-at-once-with-batch-operations). + Append multiple rows to the table `table_name` at once. This function can't operate more than 1000 rows at once. To handle more than 1000 rows, use a loop with offset or an [SQL query](/sql/) which supports up to 10,000 rows. ``` python base.batch_append_rows(table_name, rows_data, apply_default=False) # (1)! @@ -467,7 +465,7 @@ You'll find below all the available methods to interact with the rows of a SeaTa !!! abstract "batch_update_rows" - Updates multiple rows in the table `table_name` at once. This function can't operate more than 1000 rows at once. If you need to deal with more than 1000 rows at once, please refer to the [common questions](../common_questions.md#dealing-with-more-than-1000-rows-at-once-with-batch-operations). + Updates multiple rows in the table `table_name` at once. This function can't operate more than 1000 rows at once. To handle more than 1000 rows, use a loop with offset or an [SQL query](/sql/) which supports up to 10,000 rows. ``` python base.batch_update_rows(table_name, rows_data) # (1)! @@ -535,7 +533,7 @@ You'll find below all the available methods to interact with the rows of a SeaTa !!! abstract "batch_delete_rows" - Delete multiple rows from the table `table_name` at once. This function can't operate more than 1000 rows at once. If you need to deal with more than 1000 rows at once, please refer to the [common questions](../common_questions.md#dealing-with-more-than-1000-rows-at-once-with-batch-operations). + Delete multiple rows from the table `table_name` at once. This function can't operate more than 1000 rows at once. To handle more than 1000 rows, use a loop with offset or an [SQL query](/sql/) which supports up to 10,000 rows. ``` python base.batch_delete_rows(table_name, row_ids) # (1)! @@ -561,3 +559,84 @@ You'll find below all the available methods to interact with the rows of a SeaTa deletion_result = base.batch_delete_rows('Table1', row_ids) print(deletion_result) ``` + +## Big Data Storage + +!!! abstract "big_data_insert_rows" + + Batch insert rows into big data storage. + + ``` python + base.big_data_insert_rows(table_name, rows_data) + ``` + + __Output__ Dict containing a single `inserted_row_count` key with the number of rows actually inserted. + + __Example__ + + ``` python + rows = [ + {'Name': "A"}, + {'Name': "B"} + ] + base.big_data_insert_rows('Table1', rows_data=rows) + ``` + +## Filter rows + +!!! abstract "filter" + + Filter rows using a condition string. Returns a QuerySet object with chainable methods: `.all()`, `.count()`, `.first()`, `.last()`, `.filter()`, `.get()`, `.delete()`, `.update()`. + + ``` python + base.filter(table_name, conditions='', view_name=None) + ``` + + __Output__ QuerySet object + + __Example__ + + ``` python + # Get all rows where Status is "Done" + queryset = base.filter('Table1', "Status = 'Done'") + rows = queryset.all() + count = queryset.count() + first = queryset.first() + + # Chain filters + queryset = base.filter('Table1', "Year = 2024").filter("Price > 100") + + # Update all matching rows + base.filter('Table1', "Status = 'Open'").update({'Status': 'Archived'}) + ``` + +!!! abstract "filter_rows" + + Filter rows using structured filter objects. Supports combining multiple filters with `And` or `Or` conjunction. + + ``` python + base.filter_rows(table_name, filters, view_name=None, filter_conjunction='And') + ``` + + - `filters`: list of filter dicts, each containing `column_name`, `filter_predicate`, and `filter_term` + - `filter_conjunction`: `'And'` or `'Or'` + + __Output__ List of row dicts + + __Example__ + + ``` python + filters = [ + { + "column_name": "Status", + "filter_predicate": "is", + "filter_term": "Done" + }, + { + "column_name": "Price", + "filter_predicate": "greater", + "filter_term": 100 + } + ] + rows = base.filter_rows('Table1', filters, filter_conjunction='And') + ``` diff --git a/docs/scripts/python/objects/tables.md b/docs/python/objects/tables.md similarity index 100% rename from docs/scripts/python/objects/tables.md rename to docs/python/objects/tables.md diff --git a/docs/python/objects/users.md b/docs/python/objects/users.md new file mode 100644 index 00000000..fb1ca499 --- /dev/null +++ b/docs/python/objects/users.md @@ -0,0 +1,48 @@ +# Users + +## Get user info + +!!! abstract "get_user_info" + + Returns the name of the user and their ID. The username you have to provide is a unique identifier ending with `@auth.local`. This is **neither** the email address of the user **nor** their name. + + ``` python + base.get_user_info(username) + ``` + + __Output__ Dict containing `id_in_org` and `name` keys + + __Example__ + + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + user_info = base.get_user_info("aea9e807bcfd4f3481d60294df74f6ee@auth.local") + print(user_info) + ``` + +## Get related users + +!!! abstract "get_related_users" + + Get a list of users related to the current base (collaborators who have access). + + ``` python + base.get_related_users() + ``` + + __Output__ List of user dicts + + __Example__ + + ``` python + from seatable_api import Base, context + + base = Base(context.api_token, context.server_url) + base.auth() + users = base.get_related_users() + for user in users: + print(user) + ``` diff --git a/docs/scripts/python/objects/views.md b/docs/python/objects/views.md similarity index 100% rename from docs/scripts/python/objects/views.md rename to docs/python/objects/views.md diff --git a/docs/clients/ruby_api.md b/docs/ruby/index.md similarity index 100% rename from docs/clients/ruby_api.md rename to docs/ruby/index.md diff --git a/docs/scripts/index.md b/docs/scripts/index.md deleted file mode 100644 index 8e4cbdd9..00000000 --- a/docs/scripts/index.md +++ /dev/null @@ -1,29 +0,0 @@ -# Scripting - -## Supported scripting languages and requirements - -Scripts are used to interact with the data in a base. SeaTable supports scripts written in JavaScript and Python. Both languages have different requirements and abilities. Let's try to summarize these to help you make the right choice depending on your needs. - -| | JavaScript | Python | -|-----------------------|-------------|------------| -| Requirements | None | Optionally `seatable-api` library or [Python Pipeline](https://admin.seatable.com/installation/components/python-pipeline/) (see Execution environment) | -| Data operations | Simple (mainly tailored for single-line operations) | More complex (more available operations, possibility of using external libraries) | -| Execution environment | In SeaTable | - In SeaTable (self-hosted with the [Python Pipeline](https://admin.seatable.com/installation/components/python-pipeline/) installed, or Cloud)
- [Locally](https://developer.seatable.com/scripts/python/common_questions/#how-to-make-the-script-support-both-local-and-cloud-run) or on a server (need to install `seatable-api` library) | -| Authentication | Not needed | Needed | -| Main advantage | - Ready to use (no requirement, no authentication)
- Rather simple (both advantage and disadvantage) | - Local execution convenient for development and debugging purposes
- Easily integrated into larger projects | - -## How to start? - -Both JavaScript and Python scripts can be composed and executed directly in a SeaTable base. - -![Screenshot of script icon in SeaTable](/media/Anlegen-eines-Skriptes.jpg) - -Here are some additional help articles from the [User Manual's scripts section](https://seatable.com/help/scripts) explaining how to create, execute and monitor a script in SeaTable: - -- [Creating and deleting a script](https://seatable.com/help/anlegen-und-loeschen-eines-skriptes/) -- [Run script manually, by button or by automation](https://seatable.com/help/skript-manuell-per-schaltflaeche-oder-automation-ausfuehren/) -- [The execution log of scripts](https://seatable.com/help/das-ausfuehrungslog-von-skripten/) - -You'll find in this manual a JavaScript and a Python section. For both of them, each chapter provides you with explanations about the available objects and methods (description with the optional arguments and one or more simple use cases). - -Multiple [JavaScript](../scripts/javascript/examples/index.md) and [Python](../scripts/python/examples/index.md) examples should help you to start immediately and get a feeling about the possibilities. diff --git a/docs/scripts/javascript/common_questions.md b/docs/scripts/javascript/common_questions.md deleted file mode 100644 index 0e6caaa9..00000000 --- a/docs/scripts/javascript/common_questions.md +++ /dev/null @@ -1,72 +0,0 @@ -# Common questions (JavaScript) - -??? question "How to output the content of a variable?" - - To output the content of a variable you should use either no punctuation mark at all (for variable only) or dollar/brackets inside backticks `` `${}` ``. - - - ```js - const myVariable = 462; - // variable-only output - output.text(myVariable); /* (1)! */ - - // prettier output formatting - output.text(`the content of my variable is ${myVariable}`); /* (2)! */ - - // Simple/Double quotes won't work as they are used to encapsulate strings - output.text("myVariable"); /* (3)! */ - output.text('myVariable'); /* (4)! */ - ``` - - 1. Returns `462` - - 2. Returns `the content of my variable is 462` - - 3. Returns `myVariable` - - 4. Returns `myVariable` - -??? question "The display of complex elements (tables, arrays of rows) is sometimes difficult to read" - - Do not hesitate to use `console.log` and to check your browser console. Otherwise, you could try to use this function (or to create your own) at the beginning of your scripts: - - ```js - function jsonPrettyFormat(json, indent=0) { - const indenterChar = " "; /* (1)! */ - if (json instanceof Array) { - output.text(indenterChar.repeat(indent) + "["); - indent += 1; - json.forEach((elem)=>jsonPrettyFormat(elem, indent)); - indent -= 1; - output.text(indenterChar.repeat(indent) + "]"); - } - else { - if (!(typeof(json)=="object")) { - output.text(indenterChar.repeat(indent) + json); - } else { - output.text(indenterChar.repeat(indent) + "{"); - indent += 1; - for (const [key, value] of Object.entries(json)) { - if (!(typeof(value)=="object")) { - output.text(indenterChar.repeat(indent) + key + ": " + value) - } else { - output.text(indenterChar.repeat(indent) + key + ": "); - indent += 1; - jsonPrettyFormat(value, indent); - } - } - indent -= 1; - output.text(indenterChar.repeat(indent) + "}"); - } - } - } - ``` - - 1. Please note that the indent character is not a classic space character as the output window of SeaTable's script editor actually trims indent spaces. - - Just call it on an object to see the result - - ```js - let rows = base.getRows('Daily expenses', 'Default View'); - jsonPrettyFormat(rows); - ``` diff --git a/docs/scripts/javascript/examples/auto-add-rows.md b/docs/scripts/javascript/examples/auto-add-rows.md deleted file mode 100644 index 20cb4ca0..00000000 --- a/docs/scripts/javascript/examples/auto-add-rows.md +++ /dev/null @@ -1,47 +0,0 @@ -{% - include-markdown "includes.md" - start="" - end="" -%} - -With JavaScript scripts, you have to ensure **before** running the script that the options you want to add (in this case `Cloud service` and `Daily office`) already exist in your "single select"-type column. - -``` javascript -// Record monthly repetitive expenses in a ledger - -const table = base.getTableByName('Daily expenses'); - -// Get date objects on the 10th and 20th of the current month -var date = new Date(); -var date10 = new Date(date.setDate(10)); -var date20 = new Date(date.setDate(20)); - -// Check if the monthly expense items have already been created and eventually create them -const AWSCondition = "Name='Amazon Cloud Service' and Date='" + base.utils.formatDate(date10) + "'"; -const feeAWSCurrentMonth = base.filter('Daily expenses', 'Default View', AWSCondition); -if (feeAWSCurrentMonth.count() == 0) { - var feeAWS = { 'Name': 'Amazon Cloud Service', - 'Date': base.utils.formatDate(date10), - 'Type': 'Cloud service', - 'Type (single select)': 'Cloud service', - }; -} -const CleanCondition = "Name='Clean' and Date='" + base.utils.formatDate(date20) + "'"; -const feeCleanCurrentMonth = base.filter('Daily expenses', 'Default View', CleanCondition); -if (feeCleanCurrentMonth.count() == 0) { - var feeClean = { 'Name': 'Clean', - 'Date': base.utils.formatDate(date20), - 'Type': 'Daily office', - 'Type (single select)': 'Daily office', - 'Fee': 260 - }; -} - -// Auto add data (if needed) -if (feeAWS) { - base.appendRow(table, feeAWS); -} -if (feeClean) { - base.appendRow(table, feeClean); -} -``` diff --git a/docs/scripts/javascript/examples/calculate-accumulated-value.md b/docs/scripts/javascript/examples/calculate-accumulated-value.md deleted file mode 100644 index 53d953f4..00000000 --- a/docs/scripts/javascript/examples/calculate-accumulated-value.md +++ /dev/null @@ -1,58 +0,0 @@ -{% - include-markdown "includes.md" - start="" - end="" -%} - -``` javascript -// Accumulates the values of the current row and the previous rows, and records the result to the current row -const tableName = 'Accumulated value'; -const viewName = 'Default View'; - -// Name of the column that records total number at a specific time -const valueColumnName = 'Value to add'; -// Name of the column that need to calculate incremental value -const incrementalColumnName = 'Incremental total'; - -const table = base.getTableByName(tableName); -const view = base.getViewByName(table, viewName); - -// If current view is a grouped view -if (view.groupbys && view.groupbys.length > 0) { - // Get group view rows - const groupViewRows = base.getGroupedRows(table, view); - - groupViewRows.map((group) => { - let incrementalTotal = 0; - group.rows.map((row, rowIndex, rows) => { - // Get current row value - const currentNumber = row[valueColumnName]; - if (!currentNumber) return; - // Calculate increment - const previousRow = rows[rowIndex - 1]; - // If there is no previousRow, set increaseCount to 0 - const previousNumber = rowIndex>0 ? incrementalTotal : 0; - const increaseCount = currentNumber + previousNumber; - incrementalTotal = increaseCount; - // Set calculated increment to row - base.updateRow(table, row, {[incrementalColumnName]: increaseCount}); - }); - }); -} else { - // Get current view rows - let incrementalTotal = 0; - const rows = base.getRows(table, view); - rows.forEach((row, rowIndex, rows) => { - // Calculate increment - const currentNumber = row[valueColumnName]; - if (!currentNumber) return; - const previousRow = rows[rowIndex - 1]; - // If there is no previousRow, set increaseCount to 0 - const previousNumber = rowIndex>0 ? incrementalTotal : 0; - const increaseCount = currentNumber + previousNumber; - incrementalTotal = increaseCount; - // Set calculated increment to row - base.updateRow(table, row, {[incrementalColumnName]: increaseCount}); - }); -} -``` \ No newline at end of file diff --git a/docs/scripts/javascript/examples/compute-attendance-statistics.md b/docs/scripts/javascript/examples/compute-attendance-statistics.md deleted file mode 100644 index e4ebb1a0..00000000 --- a/docs/scripts/javascript/examples/compute-attendance-statistics.md +++ /dev/null @@ -1,83 +0,0 @@ -{% - include-markdown "includes.md" - start="" - end="" -%} - -``` javascript -// Computes, from a list of clocking times, daily clock in (earliest clocking) -// and clock out (latest clocking) times for each day and staff member -const originTableName = 'Clocking table'; -const originViewName = 'Default View'; -const originNameColumnName = 'Name'; -const originDepartmentColumnName = 'Department'; -const originDateColumnName = 'Date'; -const originTimeColumnName = 'Clocking time'; - -const targetTableName = 'Attendance statistics'; -const targetNameColumnName = 'Name'; -const targetDepartmentColumnName = 'Department'; -const targetDateColumnName = 'Date'; -const targetStartTimeColumnName = 'Clock-in'; -const targetEndTimeColumnName = 'Clock-out'; -const targetTable = base.getTableByName(targetTableName); - -const table = base.getTableByName(originTableName); -const view = base.getViewByName(table, originViewName); -const rows = base.getRows(table, view); - -// Sort the rows in the table according to the date column; -rows.sort((row1, row2) => { - if (row1[originDateColumnName] < row2[originDateColumnName]) { - return -1; - } else if (row1[originDateColumnName] > row2[originDateColumnName]) { - return 1; - } else { - return 0; - } -}); - -/* - Group all rows via date and save them to groupedRows, the format - of the object is {'2020-09-01': [row, ...], '2020-09-02': [row, ...]} -*/ -const groupedRows = {}; -rows.forEach((row) => { - const date = row[originDateColumnName]; - if (!groupedRows[date]) { - groupedRows[date] = []; - } - groupedRows[date].push(row); -}); - -const dateKeys = Object.keys(groupedRows); - -// Traverse all the groups in groupedRows -dateKeys.forEach((dateKey) => { - // Get all clocking data of all members for the current date - const dateRows = groupedRows[dateKey]; - const staffDateStatItem = {}; - // Traverse these rows and group by the name of the employee, get the clock-in and clock-out time of each employee that day, and save it to staffDateStatItem - // the format is { EmployeeName: {Name: 'EmployeeName', Date: '2020-09-01', Clock-in: '08:00', Clock-out: '18:00'},... } - dateRows.forEach((row)=> { - const name = row[originNameColumnName]; - if (!staffDateStatItem[name]) { - // Generate a new row based on the original row data, and add Clock-in and Clock-out columns in the newly generated row - staffDateStatItem[name] = { [targetNameColumnName]: name, [targetDateColumnName]: row[originDateColumnName], [targetDepartmentColumnName]: row[originDepartmentColumnName], [targetEndTimeColumnName]: row[originTimeColumnName], [targetStartTimeColumnName]: row[originTimeColumnName]}; - } else { - // When another record (same employee and same date) is found, compare the time, choose the latest one as the Clock-out time, and the earliest one as the Clock-in time - const time = row[originTimeColumnName]; - const staffItem = staffDateStatItem[name]; - if (staffItem[targetStartTimeColumnName] > time) { - staffItem[targetStartTimeColumnName] = time; - } else if (staffItem[targetEndTimeColumnName] < time) { - staffItem[targetEndTimeColumnName] = time; - } - } - }); - // Write the attendance data of all employees on the current date into the table - Object.keys(staffDateStatItem).forEach((name) => { - base.appendRow(targetTable, staffDateStatItem[name]); - }); -}); -``` \ No newline at end of file diff --git a/docs/scripts/javascript/examples/index.md b/docs/scripts/javascript/examples/index.md deleted file mode 100644 index 226a3641..00000000 --- a/docs/scripts/javascript/examples/index.md +++ /dev/null @@ -1,27 +0,0 @@ -# Examples - -This documentation currently contains three easy-to-follow examples of JavaScript scripts. - -{% - include-markdown "includes.md" - start="" - end="" -%} - -## Add rows - -This script demonstrates how to add rows to record monthly repetitive expenses in a ledger. - -[read more :material-arrow-right-thin:](/scripts/javascript/examples/auto-add-rows/) - -## Calculate accumulated value - -This script computes an accumulated value (adds the value of the current row and the previous rows), similar to the *Calculate accumulated value* operation from the data processing menu. - -[read more :material-arrow-right-thin:](/scripts/javascript/examples/calculate-accumulated-value/) - -## Statistics - -This script computes, from a list of clocking times, daily clock in (earliest clocking) and clock out (latest clocking) times for each day and staff member. - -[read more :material-arrow-right-thin:](/scripts/javascript/examples/compute-attendance-statistics/) diff --git a/docs/scripts/javascript/objects/columns.md b/docs/scripts/javascript/objects/columns.md deleted file mode 100644 index 883e4a67..00000000 --- a/docs/scripts/javascript/objects/columns.md +++ /dev/null @@ -1,139 +0,0 @@ -# Columns - -You'll find below all the available methods to interact with the columns of a SeaTable table. - -{% - include-markdown "includes.md" - start="" - end="" -%} - -## Get Column(s) - -!!! abstract "getColumnByName" - - Get the column object of a particular `table`, specified by the column `name`. - - ``` js - base.getColumnByName(table: Object/String/* (1)! */, name: String); - ``` - - 1. `table`: either a table object or the table name - - __Output__ Single column object (`undefined` if column `name` doesn't exist) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - const column = base.getColumnByName(table, 'Column name'); - output.text(column.name); - ``` - - ``` js - const column = base.getColumnByName('Table1', 'Column name'); - ``` - -!!! abstract "getColumns" - - Get all the columns of a specific `table`. - - ``` js - base.getColumns(table: Object/String/* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - __Output__ Array of column objects (throws an error if `table` doesn't exist) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - const columns = base.getColumns(table); - - columns.forEach((column) => { - output.text(column.name); - }) - ``` - - ``` js - const columns = base.getColumns('Table1'); - ``` - -!!! abstract "listColumns" - - Get the columns of a table (specified by its name `tableName`), optionally from a specific view (specified by its name `viewName`). If `viewName` is not set, all columns of the table will be returned (equivalent, in this case, to `base.getColumns`). - - ``` js - base.listColumns(tableName: String, viewName: String); - ``` - - __Output__ Array of column objects (throws an error if `table` doesn't exist) - - __Example__ - ``` js - const tableName = 'Table1' - const viewName = 'Default View' - const columns = base.listColumns(tableName, viewName); - - columns.forEach((column) => { - output.text(column.name); - }) - ``` - - ``` js - const columns = base.listColumns('Table1'); - ``` - -!!! abstract "getShownColumns" - - Get all the columns of a `table` displayed in a specific `view` (hidden columns are not returned). - - ``` js - base.getShownColumns(table: Object/String, view: Object/String/* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - `view` (required): either a view object or the view name - - __Output__ Array of column objects (throws an error if `table` or `view` doesn't exist) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - const view = base.getViewByName(table, 'Default View'); - const columns = base.getShownColumns(table, view); - columns.forEach((column) => { - output.text(column.name); - }) - ``` - - ``` js - const columns = base.getShownColumns('Table1', 'Default View'); - ``` - -!!! abstract "getColumnsByType" - - Get all the columns of a specific `type` in a `table`. - - ``` js - base.getColumnsByType(table: Object/String, type: String /* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - `type` (required): the type of the column (see the [API Reference](https://api.seatable.com/reference/models#supported-column-types) for supported types) - - __Output__ Array of column objects (empty array if no corresponding columns or wrong `type`) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - const columns = base.getColumnsByType(table, 'text'); - output.text(columns.length); - ``` - - ``` js - const columns = base.getColumnsByType('Table1', 'text'); - output.text(columns.length); - ``` diff --git a/docs/scripts/javascript/objects/context.md b/docs/scripts/javascript/objects/context.md deleted file mode 100644 index 4a1dcba7..00000000 --- a/docs/scripts/javascript/objects/context.md +++ /dev/null @@ -1,37 +0,0 @@ -# Context - -When the script runs, the `context` object provides context-related elements. The usage is as follows. - -!!! abstract "currentTable" - - Currently selected table. - - ``` js - base.context.currentTable; - ``` - - __Output__ The name of the currently selected table - - __Example__ - - ``` js - const name = base.context.currentTable; - output.text(`The name of the current table is: ${name}`); - ``` - -!!! abstract "currentRow" - - Currently selected row. If the script is launched from a button click, this is the row on which the button was clicked. - - ``` js - base.context.currentRow; - ``` - - __Output__ Complete row object, including `_id`, `_mtime`, `_ctime`. If no row is selected, this function returns `undefined`. - - __Example__ - - ``` js - const row = base.context.currentRow; - output.text(row); - ``` diff --git a/docs/scripts/javascript/objects/index.md b/docs/scripts/javascript/objects/index.md deleted file mode 100644 index b16df72b..00000000 --- a/docs/scripts/javascript/objects/index.md +++ /dev/null @@ -1,132 +0,0 @@ -# Predefined objects in SeaTable (JavaScript) - -The JavaScript scripts run directly in the current browser and are suitable for simple data processing. JavaScript does not require any authentication. - -This manual list all available objects and methods (also called functions) that are available within JavaScript scripts in SeaTable. On top, normal JavaScript operations like `console.log` or calculations are working as usual. By running directly in SeaTable, JavaScript scripts have the ability to access the [base context](./context.md). [Base utilities](./utilities.md) and specific [output methods](./output.md) are also available. Unless otherwise stated, **all method arguments are required**. - -!!! warning "Two JavaScript APIs in SeaTable" - - SeaTable offers two different ways to use JavaScript with SeaTable. You can execute a JavaScript script directly in SeaTable and there is a JavaScript Client API. The functions are similar but not identical. - If you want to use a JavaScript script in SeaTable, stay at this section, otherwise switch to the [Client APIs](/clients/javascript/javascript_api). - -## Data model - -{% - include-markdown "includes.md" - start="" - end="" -%} - -!!! info "Need a specific function?" - - The JavaScript class does not yet cover all available functions of the SeaTable API. If you are missing a special function, please contact us at [support@seatable.io](mailto:support@seatable.io) and we will try to add the missing functions. - -## Getting started - -Let's have a look at some basic examples. You will learn that it is quite easy to read, output and even manipulate the data of a base inside SeaTable with the predefined objects and the corresponding methods. Here is how to run these examples in your environment: - -1. Jump to your SeaTable web interface -2. Create a new Script of the type `JavaScript` -3. Copy the following code (you might have to change tables' or columns' names) -4. Run the script - -=== "Get number of tables" - - ``` js - const tables = base.getTables(); // (1)! - output.text(tables.length); // (2)! - ``` - - 1. 1. `base` is the predefined-object provided by SeaTable containing all tables of a base. - 1. `getTables()` is the function to get all tables from the object `base`. - - 2. 1. `output` is also a predefined-object provided by SeaTable. - 1. `length` is just a pure JavaScript property. - - As you can see, the script will output the number of tables in your base. Read the comments at the end of both lines to get more information about the difference between a predefined object, a function of this object and a pure JavaScript property. - -=== "Get column names" - - ```js - const table = base.getTableByName('Table1'); // (1)! - const columns = base.getColumns(table); // (2)! - - for (var i=0; i>> new row <<<') - output.text(row); - output.text(row['Name']); - } - ``` - - 1. get the content of the table `Table1` (replace `Table1` with your actual table name). - 2. get the content of the view `Default View`. - 3. get the rows displayed in the view `Default View` of the table `Table1`. - 4. iterate over all rows and print them - - This time, we will get content of the `Name` column for each row displayed in the view `Default View` of the table `Table1`. - -=== "Write new row" - - ``` js - const table = base.getTableByName('Table1'); // (1)! - - const newRow = { // (2)! - 'Name': 'Hugo', - 'Age': 3, - }; - - try { - const row = base.addRow(table, newRow); - output.text(`New row added with _id: ${row._id}`); - } catch (error) { - output.text(`Error adding row: ${error}`); - } - ``` - - 1. get the content of the table `Table1` (replace `Table1` with your actual table name). - 2. create an object containing column names `Name` and `Age` and the values you would like to set. - -=== "Update one specific row" - - ``` js - // Get the table - const table = base.getTableByName('Table1'); - - // Specify the row_id you want to update - const rowId = 'KDW9PZMkTOuwtx71pmAMxA'; // (1)! - - // Define the updates you want to make - // Replace 'Name' with the actual column name you want to update - // and 'NewValue' with the new value you want to set - // You can define more key:value pairs if you want to update - // several values of the row at the same time - const updates = { - 'Name': 'NewValue' - }; - - base.updateRow(table, rowId, updates); // (2)! - ``` - - 1. define the id of the row you want to modify. You can also use `base.context.currentRow;` to access the current (selected) row. - 2. update each values contained in the object `updates` of the row whose id is `rowId` in the table `Table1`. - - Do not hesitate to write comments in your code. It will help you (or others) to understand it more easily afterwards. diff --git a/docs/scripts/javascript/objects/links.md b/docs/scripts/javascript/objects/links.md deleted file mode 100644 index 6f527a3e..00000000 --- a/docs/scripts/javascript/objects/links.md +++ /dev/null @@ -1,223 +0,0 @@ -# Links - -!!! warning "link id and column key" - - `linkId` should not be mistaken with the column `key`! The `key` value is unique (like an id) whereas the link id will be shared between the two linked columns. Please note that `linkId` is used as argument to add/update/remove links, whereas you'll have to provide `linkColumnKey` (the link column `key`) to get linked records. Both information are available in the column object: - - ```json - { -  "key": "Cp51", /* (1)! */ -  "type": "link", -  "name": "Link column", -  "editable": true, -  "width": 200, -  "resizable": true, -  "draggable": true, -  "data": { -   "display_column_key": "0000", -   "is_internal": true, -   "link_id": "UAmR", /* (2)! */ -   "table_id": "FJkA", /* (3)! */ -   "other_table_id": "nw8k", /* (4)! */ -   "is_multiple": true, -   "is_row_form_view": false, -   "view_id": "", -   "array_type": "text", -   "array_data": null, -   "result_type": "array" -  }, -  "permission_type": "", -  "permitted_users": [], -  "permitted_group": [], -  "edit_metadata_permission_type": "", -  "edit_metadata_permitted_users": [], -  "edit_metadata_permitted_group": [], -  "description": null, -  "colorbys": {} - } - ``` - - 1. The column `key` (referred as `linkColumnKey` in `base.getLinkedRecords` arguments) - - 2. The link id of the column (referred as `linkId` in the add/update/remove link(s) methods arguments) - - 3. The table whose id is `table_id` is referred later in this section as the *source* table (the table containing this column) - - 4. The table whose id is `other_table_id` is referred later in this section as the *target* table - -## Get linkId - -!!! abstract "getColumnLinkId" - - Get the link id of the column `columnName` from the table `tableName`. - - ```js - base.getColumnLinkId(tableName: String, columnName: String); - ``` - - __Output__ String (throws an error if table `tableName` or column `columnName` doesn't exist) - - __Example__ - - ```js - base.getColumnLinkId('Table1', 'Table2 link'); - ``` - -## Get linked records - -!!! info "Rows and records, source and target" - - Rows and records are basically the same things. However, to make the following description easier to understand, we will differentiate them: - - - Rows are from the *source* table (the table whose id is `tableId`) - - - Records are the rows from the *target* table (the table linked to the *source* table in the column whose `key` is `linkColumnKey` or whose link id is `linkId`) - -!!! abstract "getLinkedRecords" - - List the records linked (in the column whose `key` is `linkColumnKey`) to one or more rows of the *source* table. The row(s) you want to get the linked records from are defined in the `linkedRows` object (see below). - - ```js - await/* (1)! */ base.getLinkedRecords(tableId: String, linkColumnKey: String, linkedRows: Object) /* (2)! */; - ``` - - 1. `await` is used for asynchronous functions. This is **required** to ensure that the following operations (or the variable where you store the results) wait for the query's response to arrive before continuing to execute the script - - 2. `tableId`: the id of *source* table - - `linkColumnKey`: the column **key** of the link-type column of *source* table (**not** the link id from `base.getColumnLinkId`) - - `linkedRows`: an array of objects, each of them containing: - - - `row_id`: the id of the row we want to get the linked records from - - - `limit`: the maximum number of linked records to get (default is 10) - - - `offset`: the number of first linked records not to retrieve (default is 0) - - __Output__ A `key`:`value` data structure where each `key` is the id of a row of the *source* table and the corresponding value is an array of link objects (see Output structure example below) - - __Example__ - - === "Function run" - - ```js - await base.getLinkedRecords('0000', '89o4', [ - {'row_id': 'FzNqJxVUT8KrRjewBkPp8Q', 'limit': 2, 'offset': 0}, - {'row_id': 'Jmnrkn6TQdyRg1KmOM4zZg', 'limit': 20} - ]); - ``` - - === "Output structure example" - - ```js - { - 'FzNqJxVUT8KrRjewBkPp8Q' /* (1)! */: [ - {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'} /* (2)! */, - {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, - ... - ], - 'Jmnrkn6TQdyRg1KmOM4zZg': [ - {'row_id': 'LocPgVvsRm6bmnzjFDP9bA', 'display_value': '1'}, - {'row_id': 'OA6x7CYoRuyc2pT52Znfmw', 'display_value': '3'}, - ... - ] - } - ``` - - 1. id of a row of the *source* table - - 2. link object: - - - `row_id` is the id of the linked record (row from the *target* table) - - `display_value` is the value displayed in the column whose `key` is `linkColumnKey` - (from a column of the *target* table) - - __Output__ Object containing the linked records for each row (see Output structure example above) - - __Example: Get linked records from current row__ - - ```js - const table = base.getTableByName('Table1'); - const linkColumn = base.getColumnByName(table, 'Table2 link'); - const currentRowLinks = await base.getLinkedRecords(table._id, linkColumn.key, [{'row_id': base.context.currentRow._id, 'limit':100 /* (1)! */}]); - currentRowLinks[base.context.currentRow._id].forEach((link) => {output.text(link)}); - ``` - - 1. `limit`:100 => the response will return maximum 100 rows - - -## Add link - -!!! abstract "addLink" - - Add link in a link-type column. You'll need the *source* table's name `tableName`, the *target* table's name `linkedTableName`, the `linkId` from the link-type column and both the ids of the rows you want to link: `rowId` for the row from the *source* table and `linkedRowId` for the record from the *target* table. - - ```js - base.addLink(linkId: String, tableName: String, linkedTableName: String, rowId: String, - linkedRowId: String); - ``` - - __Output__ Nothing - - __Example__ - - ```js - base.addLink('5WeC', 'Team Members', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ'); - ``` - - __Example: Add link to current row__ - - ```js - // Do not hesitate to store the tables' and columns' names at the beginning of your script, - // it will make it really easier to update if names change - const table1Name = "Table1"; - const table1LinkColumnName = "Table2 link"; - const table2Name = "Table2"; - - const linkId = base.getColumnLinkId(table1Name, table1LinkColumnName); /* (1)! */ - const currentRowId = base.context.currentRow._id; - base.addLink(linkId, table1Name, table2Name, currentRowId, 'J5St2clyTMu_OFf9WD8PbA'); - ``` - - 1. Remember you can use `base.getColumnLinkId` to get the link id of a specific link-type column. - - -## Update link(s) - -!!! abstract "updateLinks" - - Update the content of the link-type column whose link id is `linkId` for the row with id `rowId` in the table `tableName`. It will remove all existing row links and add new links to records of table `linkedTableName` with ids listed in the `updatedlinkedRowIds` array. - - ```js - base.updateLinks(linkId, tableName, linkedTableName, rowId, updatedlinkedRowIds: Array of String); - ``` - - __Output__ Nothing - - __Example__ - - ```js - const records = base.getRows('Contacts', 'Default_view'); - // Update links for row from "Team Members" with _id CGtoJB1oQM60RiKT-c5J-g to [records[0]._id, records[1]._id, records[2]._id, records[3]._id] - // Real-life tip: ensure that the array "records" actually contains at least 4 elements! - base.updateLinks('5WeC', 'Team Members', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', [records[0]._id, records[1]._id, records[2]._id, records[3]._id]); - ``` - -## Remove link - -!!! abstract "removeLink" - - Delete the link to the record from table `linkedTableName` whose id is `linkedRowId` in the row from table `tableName` whose id is `rowId`. Every arguments are `String`. - - ```js - base.removeLink(linkId, tableName, linkedTableName, rowId, linkedRowId); - ``` - - __Output__ Nothing - - __Example__ - - ```js - base.removeLink('5WeC', 'Team Members', 'Contacts', 'CGtoJB1oQM60RiKT-c5J-g', 'PALm2wPKTCy-jdJNv_UWaQ'); - ``` diff --git a/docs/scripts/javascript/objects/output.md b/docs/scripts/javascript/objects/output.md deleted file mode 100644 index 0a5ad4b4..00000000 --- a/docs/scripts/javascript/objects/output.md +++ /dev/null @@ -1,37 +0,0 @@ -# Output - -Two functions are available to display results in the text editor window, allowing you to output strings in text or Markdown format. - -!!! abstract "text" - - Prints the content of `anything` as normal text. Code Syntax is ignored and just printed. - - ``` js - output.text(anything: String/Object/Array) - ``` - - __Output__ String - - __Example__ - - ``` js - const table = base.getActiveTable(); - output.text(table.name); - ``` - -!!! abstract "markdown" - - Prints the content of `anything`, while using Markdown formatting to style the output. - - ``` js - output.markdown(anything: String/Object/Array) - ``` - - __Output__ String - - __Example__ - - ``` js - const table = base.getActiveTable(); - output.markdown(`# This is a headline and prints the name of the table: ${table.name}`); - ``` diff --git a/docs/scripts/javascript/objects/rows.md b/docs/scripts/javascript/objects/rows.md deleted file mode 100644 index bd0acc4a..00000000 --- a/docs/scripts/javascript/objects/rows.md +++ /dev/null @@ -1,889 +0,0 @@ -# Rows - -You'll find below all the available methods to interact with the rows of a SeaTable table. In this section, you'll have to deal with the id of the rows. You can find few tips on how to get it in [the user manual](https://seatable.com/help/was-ist-die-zeilen-id/). - -{% - include-markdown "includes.md" - start="" - end="" -%} - -## Get row(s) - -!!! abstract "getRow / getRowById (deprecated)" - - Get a `table`'s row via its id `rowId`. - - ``` js - base.getRow(table: Object/String /* (1)! */, rowId: String); - ``` - - 1. `table`: either a table object or the table name - - __Output__ Single row object (throws an error if `table` doesn't exist or if no row with the specified id `rowId` exists) - - __Example__ - - ``` js - const table = base.getTableByName('Table1'); - const row = base.getRow(table, "M_lSEOYYTeuKTaHCEOL7nw"); - ``` - - ``` js - const row = base.getRow('Table1', "M_lSEOYYTeuKTaHCEOL7nw"); - ``` - -!!! abstract "getRows" - - Get all the rows displayed in the `view` of a `table`. - - ``` js - base.getRows(table: Object/String, view: Object/String /* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - `view` (required): either a view object or the view name - - __Output__ Array of row objects (throws an error if `table` or `view` doesn't exist) - - __Example__ - - ``` js - const table = base.getTableByName('Table1'); - const view = base.getViewByName(table, 'Default View'); - const rows = base.getRows(table, view); - - rows.forEach((row) => { - output.text(row._id); - }) - ``` - - ``` js - const rows = base.getRows('Table1', 'Default View'); - ``` - -!!! abstract "query" - - Use SQL to query a base. SQL queries are the most powerful way access data stored in a base. If you're not familiar with SQL syntax, we recommend using first the [SQL query plugin](https://seatable.com/help/anleitung-zum-sql-abfrage-plugin/). Most SQL syntax is supported, you can check the [SQL Reference](/scripts/sql/introduction.md) section of this manual for more information. - - ``` js - await/* (1)! */ base.query(sqlStatement: String); - ``` - - 1. `await` is used for asynchronous functions. This is **required** to ensure that the following operations (or the variable where you store the results) wait for the query's response to arrive before continuing to execute the script - - !!! info "Backticks for table or column names containing special characters or using reserved words" - For SQL queries, you can use numbers, special characters or spaces in the names of your tables and columns. However, you'll **have to** escape these names with backticks in order for your query to be correctly interpreted, for example `` SELECT * FROM `My Table` ``. - - Similarly, if some of your table or column names are the same as [SQL function](/scripts/sql/functions.md) names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. - - __Output__ Array of row objects (single empty object if no row match the request's conditions) - - All the examples below are related to a table **Bill** with the following structure/data: - - | name | price | year | - | ----- | ----- | ----- | - | Bob | 300 | 2021 | - | Bob | 300 | 2019 | - | Tom | 100 | 2019 | - | Tom | 100 | 2020 | - | Tom | 200 | 2021 | - | Jane | 200 | 2020 | - | Jane | 200 | 2021 | - - - __Example: Get everything with a wildcard__ - - === "Function call" - - ``` js - const data = await base.query('select * from Bill');/* (1)! */ - output.text(data); - ``` - - 1. `*` means that you want to get the whole rows data (columns's values and specific row data such as id, etc.) - - === "Output" - - ```json - [ - { - "name": "Bob", - "price": 300, - "year": 2021, - "_locked": null, - "_locked_by": null, - "_archived": false, - "_creator": "bd26d2b...82ca3fe1178073@auth.local", - "_ctime": "2025-09-15T10:57:19.106+02:00", - "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", - "_mtime": "2025-09-18T09:52:00+02:00", - "_id": "W77uzH1cSXu2v2UtqA3xSw" - }, - { - "name": "Bob", - "price": 300, - "year": 2019, - "_locked": null, - "_locked_by": null, - "_archived": false, - "_creator": "bd26d2b...82ca3fe1178073@auth.local", - "_ctime": "2025-09-15T10:57:22.112+02:00", - "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", - "_mtime": "2025-09-18T09:52:00+02:00", - "_id": "IxONgyDFQxmcDKpZWlQ9XA" - }, - { - "name": "Tom", - "price": 100, - "year": 2019, - "_locked": null, - "_locked_by": null, - "_archived": false, - "_creator": "bd26d2b...82ca3fe1178073@auth.local", - "_ctime": "2025-09-15T10:57:23.4+02:00", - "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", - "_mtime": "2025-09-18T09:52:00+02:00", - "_id": "K4LBuQ7aSjK9JwN14ITqvA" - }, - { - "name": "Tom", - "price": 100, - "year": 2020, - "_locked": null, - "_locked_by": null, - "_archived": false, - "_creator": "bd26d2b...82ca3fe1178073@auth.local", - "_ctime": "2025-09-18T09:52:00+02:00", - "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", - "_mtime": "2025-09-18T09:52:00+02:00", - "_id": "EHcQEaxiRzm3Zvq8B33bwQ" - }, - { - "name": "Tom", - "price": 200, - "year": 2021, - "_locked": null, - "_locked_by": null, - "_archived": false, - "_creator": "bd26d2b...82ca3fe1178073@auth.local", - "_ctime": "2025-09-18T09:52:00+02:00", - "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", - "_mtime": "2025-09-18T09:52:00+02:00", - "_id": "CjaCdBlNRXKkYkm231shqg" - }, - { - "name": "Jane", - "price": 200, - "year": 2020, - "_locked": null, - "_locked_by": null, - "_archived": false, - "_creator": "bd26d2b...82ca3fe1178073@auth.local", - "_ctime": "2025-09-18T09:52:00+02:00", - "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", - "_mtime": "2025-09-18T09:52:00+02:00", - "_id": "YzmUexIAR7iDWmhKGHgpMw" - }, - { - "name": "Jane", - "price": 200, - "year": 2021, - "_locked": null, - "_locked_by": null, - "_archived": false, - "_creator": "bd26d2b...82ca3fe1178073@auth.local", - "_ctime": "2025-09-18T09:52:00+02:00", - "_last_modifier": "bd26d2b...82ca3fe1178073@auth.local", - "_mtime": "2025-09-18T09:52:00+02:00", - "_id": "HJi7wbUMQIOuIlPaoO9Fbg" - } - ] - ``` - - __Example with WHERE__ - - === "Function call 1 (filter by year)" - - ``` js - const data = await base.query('select name, price from Bill where year = 2021'); - output.text(data); - ``` - - === "Output #1" - - ```json - [ - {"name":"Bob","price":"300"}, - {"name":"Tom","price":"200"}, - {"name":"Jane","price":"200"} - ] - ``` - - === "Function call 2 (filter by name)" - - ```js - const data = await base.query('select name, price, year from Bill where name = "Bob"'); - output.text(data); - ``` - - === "Output #2" - - ```json - [ - {"name":"Bob","price":"300","year":"2021"}, - {"name":"Bob","price":"300","year":"2019"} - ] - ``` - - - __Example with GROUP BY__ - - === "Function call" - - ``` js - const data = await base.query('select name, sum(price) from Bill group by name'); - output.text(data); - ``` - - === "Output" - - ```json - [ - {'name': 'Bob', 'SUM(price)': 600}, - {'name': 'Tom', 'SUM(price)': 400}, - {'name': 'Jane', 'SUM(price)': 400} - ] - ``` - - __Example with DISTINCT__ - - === "Function call" - - ``` js - const data = await base.query('select distinct name from Bill'); - output.text(data); - ``` - - === "Output" - - ```json - [ - {'name': 'Bob'}, - {'name': 'Tom'}, - {'name': 'Jane'} - ] - ``` - -!!! abstract "getGroupedRows" - - Get rows in the grouped `view` of a `table`. - - ``` js - base.getGroupedRows(table: Object/String, view: Object/String /* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - `view` (required): either a view object or the view name - - __Output__ Array of grouped rows object (see Output example below) - - __Example__ - - === "Function call" - - ``` js - const table = base.getTableByName('Table1'); - const view = base.getViewByName(table, 'GroupedView'); - const groupViewRows = base.getGroupedRows(table, view); - ``` - - === "Output example" - - ```json - [ - { /* (1)! */ - "column_name": "date", - "column_key": "tc2B", - "cell_value": "2025-09", - "rows": [], /* (2)! */ - "subgroups": [ - { - "column_name": "Val2", - "column_key": "7Q0G", - "cell_value": 462, - "rows": [ - { - "bjcM": 12, - "0000": "John", - "7Q0G": 462, - "tc2B": "2025-09-11", - "Tm99": "520035", - "_creator": "aa", - "_last_modifier": "cc7a1d0fce...b65b99@auth.local", - "_id": "AGO_2SiiTY61uMr-tTVGvQ", - "_ctime": "2025-09-11T07:38:23.082+00:00", - "_mtime": "2025-09-11T09:28:32.204+00:00", - "mpxK": 0 - }, - { - "bjcM": 12, - "0000": "John", - "7Q0G": 462, - "tc2B": "2025-09-11", - "Tm99": "520035", - "_creator": "aa", - "_last_modifier": "cc7a1d0fce...b65b99@auth.local", - "_id": "WTu6o6lxS-ChnamkU1wjuA", - "_ctime": "2025-09-11T07:39:10.297+00:00", - "_mtime": "2025-09-11T09:28:32.204+00:00", - "mpxK": 0 - } - ], - "subgroups": [] /* (3)! */ - } - ] - }, - { - "column_name": "date", - "column_key": "tc2B", - "cell_value": null, - "rows": [], - "subgroups": [ - { - "column_name": "Val2", - "column_key": "7Q0G", - "cell_value": 4, - "rows": [ - { - "_id": "GIgxrz8VSzm-aHSbJ6_i4w", - "_participants": [], - "_creator": "cc7a1d0fce...b65b99@auth.local", - "_ctime": "2025-09-03T07:03:57.838+00:00", - "_last_modifier": "cc7a1d0fce...b65b99@auth.local", - "_mtime": "2025-09-17T15:31:04.150+00:00", - "bjcM": 1, - "0000": "name", - "7Q0G": 4, - "plxx": 5676, - "Tm99": "207110", - "mpxK": "" - }, - { - "_id": "PSfpr9dzRPaKUeIn-3va0w", - "_participants": [], - "_creator": "cc7a1d0fce...b65b99@auth.local", - "_ctime": "2025-09-03T07:03:57.838+00:00", - "_last_modifier": "cc7a1d0fce...b65b99@auth.local", - "_mtime": "2025-09-11T09:28:32.204+00:00", - "bjcM": 0, - "0000": "zu", - "7Q0G": 4, - "plxx": 3872, - "Tm99": "375528", - "mpxK": 0 - } - ], - "subgroups": [] - }, - { - "column_name": "Val2", - "column_key": "7Q0G", - "cell_value": 9, - "rows": [ - { - "_id": "H3djeRnkQdWhKBhEG2cGUw", - "_participants": [], - "_creator": "cc7a1d0fce...b65b99@auth.local", - "_ctime": "2025-09-03T07:03:57.838+00:00", - "_last_modifier": "cc7a1d0fce...b65b99@auth.local", - "_mtime": "2025-09-11T09:28:32.204+00:00", - "bjcM": 3, - "0000": "a", - "7Q0G": 9, - "plxx": 1668, - "Tm99": "520035", - "mpxK": 0 - }, - { - "_id": "ARedNyn8R7CZFmRushZmvQ", - "_participants": [], - "_creator": "cc7a1d0fce...b65b99@auth.local", - "_ctime": "2025-09-03T08:23:03.776+00:00", - "_last_modifier": "cc7a1d0fce...b65b99@auth.local", - "_mtime": "2025-09-17T15:31:09.842+00:00", - "0000": "b", - "bjcM": "", - "7Q0G": 9, - "plxx": 610, - "Tm99": "211464", - "mpxK": 0 - }, - { - "_id": "L4IWGz4hT3qb1_u9bBbvFg", - "_participants": [], - "_creator": "cc7a1d0fce...b65b99@auth.local", - "_ctime": "2025-09-03T14:03:51.524+00:00", - "_last_modifier": "cc7a1d0fce...b65b99@auth.local", - "_mtime": "2025-09-17T15:31:08.429+00:00", - "0000": "name", - "bjcM": 15, - "7Q0G": 9, - "plxx": 565, - "Tm99": "745764", - "mpxK": 0 - } - ], - "subgroups": [] - } - ] - } - ] - ``` - - 1. Grouped rows object containing either `rows` or `subgroups` (array of grouped rows objects) in the case of multiple grouping rules - - 2. No `rows`: this grouped rows object only contains `subgroups` (member of the first grouping rule) - - 3. No `subgroups`: this grouped rows object only contains `rows` (member of the last grouping rule) - - ``` js - const groupViewRows = base.getGroupedRows('Table1', 'GroupedView'); - ``` - -## Add row - -!!! abstract "appendRow / addRow (deprecated)" - - Add a row to a `table`. This row contains the data specified in the object `rowData`. The row will be empty if `rowData` is empty or if it contains only keys that don't exist in the `table`. - - ``` js - base.appendRow(table: Object/String, rowData: Object, viewName: String /* (1)! */) - ``` - - 1. `table`: either a table object or the table name - - `rowData`: object (pairs of `key`:`value`, each `key` being the name of a column), for example: - - ``` - { - 'First Name': 'John', - 'Last Name': 'Doe', - 'Invoice amount': 100, - 'Products': ['Office Supplies', 'Computer'] - } - ``` - - __Output__ Single row object (throws an error if `table` doesn't exist) - - __Example__ - - ``` js - const table = base.getTableByName('Table1'); - base.appendRow(table, {'Name': 'Alex', 'Age': '18'}); - base.appendRow(table, {'Name': 'Alex', 'Age': '18'}, 'Default View'); - ``` - -## Update row(s) - -!!! abstract "updateRow / modifyRow(deprecated)" - - Modify a `row` in the `table`. The `updateRowData` object (pairs of `key`:`value`, each `key` being the name of a column) need to contain only the data you want to update. To reset a value, specify the `key`:`value` pair with an empty string `''`. - - ``` js - base.updateRow(table: Object/String, row: Object/String, updateRowData: Object /* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - `row`: either a row object or the row id - - __Output__ Nothing (throws an error if `table` doesn't exist or if no row with the specified id exists) - - __Example__ - - ``` js - const table = base.getTableByName('Table1'); - const row = base.getRow(table, "M_lSEOYYTeuKTaHCEOL7nw"); - base.updateRow(table, row, {'Name': 'new name', 'number': 100}); - ``` - - ``` js - base.updateRow('Table1', 'U_eTV7mDSmSd-K2P535Wzw', {'Name': 'new name', 'number': 100}) - ``` - -!!! abstract "modifyRows" - - Modify multiple `rows` in the `table` at once. `updatedRows` is an array of `updateRowData` objects (see above). Please note that `rows` only accepts an array of row objects (and not of ids). - - ``` js - base.modifyRows(table: Object/String, rows: Array of Object, updatedRows: Array of Object /* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - `rows`: array of row objects **only** (not row ids) - - __Output__ Nothing (throws an error if `table` doesn't exist or if one row in `rows` doesn't exists) - - __Example__ - - ``` js - const table = base.getTableByName('Table1'); - const rows = base.getRows(table, 'Default View'); - const selectedColumnName = 'Name'; - const selectedRows = [], updatedRows = []; - - rows.forEach((row) => { - if (row[selectedColumnName] === 'name') { - selectedRows.push(row); - updatedRows.push({[selectedColumnName]: 'name1'}); - } - }); - base.modifyRows(table, selectedRows, updatedRows); - ``` - - ``` js - base.modifyRows('Table1', [base.getRow('Table1','GIgxrz8VSzm-aHSbJ6_i4w'),base.getRow('Table1','PSfpr9dzRPaKUeIn-3va0w')], [{'Name': 'name'},{'Name': 'name'}]); - ``` - -## Delete row - -!!! abstract "deleteRow / deleteRowById (deprecated)" - - Delete a row in a `table` by its id `rowId`. - - ``` js - base.deleteRow(table: Object/String, rowId: String /* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - `rowId`: the id of the row to delete - - - __Output__ Nothing (no error if no row with id `rowId` exists) - - __Example__ - - ``` js - const table = base.getTableByName('Table1'); - base.deleteRow(table, 'M_lSEOYYTeuKTaHCEOL7nw'); - ``` - - ``` js - base.deleteRow('Table1', 'M_lSEOYYTeuKTaHCEOL7nw'); - ``` - - - -## Filter - -!!! abstract "filter" - - Filters the rows displayed in the view `viewName` of the `table` that meet the conditions of the `filterExpression` (conditional statement), and returns a querySet object. See the `filterExpression` reference below for more details. - - ``` js - base.filter(tableName: String, viewName: String, filterExpression: String); - ``` - - __Output__ Single querySet object (see below), the `rows` array being empty if no row meet the `filterExpression` conditions - - __Example__ - - === "Function call" - - ``` js - // Filter out rows whose number column is equal to 5, and return a querySet object - const querySet = base.filter('Table1', 'Default View', 'number = 5'); - ``` - - === "Output structure" - - ```json - { - "rows": [ /* (1)! */ - ... - ], - "table": { /* (2)! */ - ... - }, - "parser": { - ... - } - } - ``` - - 1. `rows`: array of the rows in the view `viewName` meeting the `filterExpression` conditions - - 2. `table`: the whole `table` object - - - ```js - const querySet = base.filter("Table1", "Default View", "age>18"/* (1)! */) - ``` - - 1. `age`: column name - - `>`: operator - - `18`: parameter - -### filterExpression reference - -!!! abstract "filterExpression" - - The most common operators are available to define the conditional statement of the `filterExpression`: - - | Type of operators | Available operators | - | ------------------ | ------------------- | - | Greater-Less comparisons | >=, >, <, <= | - | Equal-Not equal comparisons | =, <> (not equal to) | - | Arithmetic operators | +, -, *, /, ^ (power), % (modulo) | - | Logical operators | and, or | - - Depending on the data type, there are slight differences in the query method and the format of input statement. Here is a list of the possible operations for each type: - - - | Data structure | Column type | Format for Greater-Less comparisons | Format for Equal-Not equal comparisons | Arithmetic operators | - | -------------- | ----------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------- | :---------- | - | String | Text, Long Text, URL, Email, Single Select | Unsupported | String | Unsupported | - | List | Multiple Select | Unsupported | String | Unsupported | - | Number | Number | Number | Number and empty String `""`"" | Supported | - | Date | Date, Created time, Last modified time | Patterns: YYYY-MM-DD, YYYY-MM-DD hh:mm, YYYY-MM-DD hh\:mm:ss | Same patterns as greater-less query | Unsupported | - | Boolean | Checkbox | Unsupported | true, false and empty String `""`, (case-insensitive) | Unsupported | - - - !!! info "Mind the quotes!" - For queries involving string-based or date-based columns, you'll have to use double quotes `" "` to define the `filterExpression` as you'll need simple quotes `' '` for the strings/dates... Or the opposite: use either `"column_name='hello world'"` or `'column_name="hello world"'` - - Here are more examples of the different filter expressions pending of the column type. - - __String-based Column__ (**Text, Long Text, URL, Email, Single Select** columns) - - - ```js - // Equal-unequal query - base.filter('Table1', 'Default View', "column_name='hello world'") - base.filter('Table1', 'Default View', "column_name!=''") - - ``` - -
- __List-based Column__ (**Multiple Select** columns) - - - ```js - // Equal-unequal query - base.filter('Table1','Default View', "column_name='A' and column_name='B'") /* (1)! */ - ``` - - 1. Find the rows which contains both 'A' and 'B' - -
- __Number-based Column__ (**Number** columns) - - === "Greater-less query" - - ```js - base.filter('Table1', 'Default View', "column_name>18") - base.filter('Table1', 'Default View', "column_name>-10 and column_name<=0") - ``` - - === "Equal-unequal query" - - ```js - base.filter('Table1', 'Default View',"column_name<>20") - base.filter('Table1', 'Default View', "column_name=0") - base.filter('Table1', 'Default View',"column_name=''") - ``` - - === "Arithmetic query" - - ```js - base.filter('Table1', 'Default View', "column_name+3>18") - base.filter('Table1', 'Default View', "column_name*2=18") - base.filter('Table1', 'Default View', "column_name-2=18") - base.filter('Table1', 'Default View', "column_name/2=18") - base.filter('Table1', 'Default View', "column_name^2=18") - base.filter('Table1', 'Default View', "column_name%2=1") - ``` - -
- __Date-based Column__ (**Date, Created time, Last modified time** columns) - - === "Greater-less query" - - ```js - base.filter('Table1', 'Default View', "column_name>'2020-1-30'") - base.filter('Table1', 'Default View', "column_name>='2019-1-1 5:30' and column_name<='2019-5-1 6:00'") - ``` - - === "Equal-unequal query" - - ```js - base.filter('Table1', 'Default View', "column_name='2020-1-1 10:59:59'") - base.filter('Table1', 'Default View', "column_name!=''") - ``` - -
- __Boolean-based Column__ (**Checkbox** columns) - - === "Equal-unequal query" - - ```js - base.filter('Table1', 'Default View','column_name=False')/* (1)! */ - base.filter('Table1', 'Default View', "column_name=True") - ``` - - 1. same as `base.filter('Table1', "column_name=''")` - -### querySet handling - -The output of the `base.filter` function is a `querySet` object. Here are the methods of this object provided to simplify the operations on the filtered data. - -!!! abstract "all" - - Returns all filtered rows of the `querySet` in the form of a list. - - ``` js - querySet.all(); - ``` - - __Output__ Array of row objects - - __Example__ - - ``` js - const querySet = base.filter('Table1', 'Default View', 'number = 5'); - const list = querySet.all(); - output.text(list); - ``` - -!!! abstract "count" - - Returns the number of filtered rows of the `querySet`. - - ``` js - querySet.count(); - ``` - - __Output__ Number - - __Example__ - - ```js - const querySet = base.filter('Table1', 'Default View', 'number = 5'); - const count = querySet.count(); - output.text(`The querySet contains ${count} rows`); - ``` - -!!! abstract "first" - - Return the first filtered row of the `querySet`. - - ``` js - querySet.first(); - ``` - - __Output__ Single row object (`undefined` if the `querySet` contains no row) - - __Example__ - - ```js - const querySet = base.filter('Table1', 'Default View', 'number = 5'); - const row = querySet.first(); - ``` - -!!! abstract "last" - - Return the last filtered row of the `querySet`. - - ``` js - querySet.last(); - ``` - - __Output__ Single row object (`undefined` if the `querySet` contains no row) - - __Example__ - - ```js - const querySet = base.filter('Table1', 'Default View', 'number = 5'); - const row = querySet.last(); - ``` - -!!! abstract "delete" - - Delete all filtered rows of the `querySet` and return the number of rows successfully deleted. - - ``` js - querySet.delete(); - ``` - - __Output__ Number - - __Example__ - - ```js - const querySet = base.filter('Table1', 'Default View', 'number = 5'); - const count = querySet.delete(); - output.text(`${count} rows successfully deleted!`); - ``` - -!!! abstract "update" - - Modify the row data according to the`rowData` Object and return the updated rows. - - ``` js - querySet.update(rowData: Object/* (1)! */); - ``` - - 1. `rowData`: object (pairs of `key`:`value`, each `key` being the name of a column) - - __Output__ Array of row objects (empty Array if no filtered row) - - __Example__ - - ```js - // Modify the content of the Name column of all filtered rows to xxxx - const querySet = base.filter('Table1', 'Default View', 'number = 5'); - const rows = querySet.update({Name: 'xxxx'}); - ``` - -!!! abstract "filter" - - Further filtering using the `filterExpression` conditional statement. - - ```js - querySet.filter(filterExpression: String); - ``` - - __Output__ Single querySet object - - __Example__ - - ```js - // Filter out the rows with the value of Tom in the Name column of querySet1 - const querySet1 = base.filter('Table1', 'Default View', 'number = 5'); - const querySet2 = querySet1.filter("Name = 'Tom'"); - ``` - -!!! abstract "get" - - Return the first row of the querySet that meets the conditions of the new `filterExpression`. This is equivalent to `querySet.filter(filterExpression).first()` - - ```js - querySet.get(filterExpression: String); - ``` - - __Output__ Single row object (`undefined` if no row meets the conditions of the `filterExpression`, `#ERROR!` if the `filterExpression` is wrong) - - __Example__ - - ```js - // Get the first data of Tom in the Name column of the querySet - const querySet = base.filter('Table1', 'Default View', 'number = 5'); - const row = querySet.get("Name = 'Tom'"); - ``` diff --git a/docs/scripts/javascript/objects/tables.md b/docs/scripts/javascript/objects/tables.md deleted file mode 100644 index af68d7a0..00000000 --- a/docs/scripts/javascript/objects/tables.md +++ /dev/null @@ -1,116 +0,0 @@ -# Tables - -You'll find below all the available methods to interact with the tables of a SeaTable base. - -{% - include-markdown "includes.md" - start="" - end="" -%} - -You can have a look at the specific [view](./views.md#global-structure), [column](./columns.md#global-structure) or [row](./rows.md#global-structure) structure on the corresponding pages. - -## Get Table(s) - -!!! abstract "getActiveTable" - - Get the currently selected table. - - ``` js - base.getActiveTable(); - ``` - __Output__ Single table object - - __Example__ - ``` js - const table = base.getActiveTable(); - output.text(`The name of the active table is: ${table.name}`); - ``` - -!!! abstract "getTables" - - Get all tables of the current base. - - ``` - base.getTables(); - ``` - __Output__ Array of table objects - - __Example__ - ``` js - const tables = base.getTables(); - output.text(tables); - ``` - -!!! abstract "getTableByName" - - Get a table object by its name. - - ``` js - base.getTableByName(tableName: String); - ``` - - __Output__ Single table object (`undefined` if table doesn't exist) - - __Example__ - - ```js - const table = base.getTableByName('Table1'); - // Display only table _id - output.text(`The id of the table is: ${table._id}`); - // Display whole table structure - output.text(table); - ``` - -## Add Table - -!!! abstract "addTable" - - Add a new table to this base, given the new table name `tableName`. Please ensure that you choose a `tableName` that doesn't already exist in your base. - - ``` js - base.addTable(tableName: String); - ``` - __Output__ Nothing - - __Example__ - ``` js - base.addTable('New table'); - output.text("Wow, I just added a new table to this base.") - ``` - -## Rename Table - -!!! abstract "renameTable" - - Rename an existing table named `oldName` to `newName`. Please ensure that you choose a `newName` that doesn't already exist in your base. - - ``` js - base.renameTable(oldName: String, newName: String); - ``` - - __Output__ Nothing (throws an error if no table named `oldName` exists) - - __Example__ - ``` js - const old_name = "Table1"; - const new_name = "Projects 2023"; - base.renameTable(old_name, new_name); - output.text(`This base ${old_name} got a new name: ${new_name}`); - ``` - -## Delete Table - -!!! abstract "deleteTable" - - Delete a table named `tableName` from the base. By the way, the table can be [restored from the logs](https://seatable.com/help/eine-geloeschte-tabelle-wiederherstellen/). Deleting the last table is not possible. - - ``` js - base.deleteTable(tableName: String); - ``` - __Output__ Nothing (throws an error if no table named `tableName` exists) - - __Example__ - ``` js - base.deleteTable('Old table'); - ``` diff --git a/docs/scripts/javascript/objects/utilities.md b/docs/scripts/javascript/objects/utilities.md deleted file mode 100644 index ecd6a105..00000000 --- a/docs/scripts/javascript/objects/utilities.md +++ /dev/null @@ -1,120 +0,0 @@ -# Utility functions - -Utility functions help you to work with data in SeaTable. - -## Date and Time - -!!! abstract "formatDate" - - Format `date` to 'YYYY-MM-DD' to be used in a date-type column. - - ``` js - base.utils.formatDate(date: Date Object) - ``` - - __Output__ String - - __Example__ - ``` js - let date = new Date(); - let formatDate = base.utils.formatDate(date); - output.text(formatDate); - ``` - -!!! abstract "formatDateWithMinutes" - - Format `date` to 'YYYY-MM-DD HH:mm' to be used in a date-type column. - - ``` js - base.utils.formatDateWithMinutes(date: date object) - ``` - - __Output__ String - - __Example__ - ``` js - let date = new Date(); - let formatDate = base.utils.formatDateWithMinutes(date); - output.text(formatDate); - ``` - -## Lookup and Query - -!!! abstract "lookupAndCopy" - - Similar to the Microsoft Excel VLOOKUP function. Find a matching row in the *source* table for each row of the *target* table, and then copy the data of the specified cell of the matching row to the specified cell of the *target* row. Every arguments are `String`. - - ``` js - base.utils.lookupAndCopy(targetTable, targetColumn, targetColumnToCompare, sourceTableName, - sourceColumnName, sourceColumnToCompare = null /* (1)! */); - ``` - - 1. `targetTable`: the name of the *target* table - i.e. the table you want to copy data **into** - - `targetColumn`: the column of `targetTable` you want to copy data into - - `targetColumnToCompare`: the column of `targetTable` you want to compare to a column of table `sourceTableName` - - `sourceTableName`: the *source* table - i.e. the table you want to copy data **from** - - `sourceColumnName`: the column of `sourceTableName` you want to copy data from - - `sourceColumnToCompare`: If specified, the column of `sourceTableName` you want to compare with `targetColumnToCompare` to find matching rows. If not specified, the system will look for a column with the name `targetColumn` in the table `sourceTableName` - - __Output__ Nothing (throws an error if some tables or columns do not exist) - - __Principle example__ - - Here are two tables, the *source* table containing both names and emails for few Avengers whereas the *target* table only has the user names. - - **Source table** - - | Name | SourceEmail | - | --- | --- | - | Hulk | greenbigboy@stark-industries.movie | - | Tony | ironman@stark-industries.movie | - - **Target table** - - | Name | TargetEmail | - | --- | --- | - | Hulk | | - | Tony | | - - To copy the email addresses from the *source* table to the *target* table, this function can be used with the following syntax: - - ``` - base.utils.lookupAndCopy('Target table', 'TargetEmail', 'Name', 'Source table', 'SourceEmail'); - ``` - - __Example__ - - ``` js - // Match the rows with the same content in the Name column of Table1 and Table2, - // copy the contents of the Email column of the row in Table1 to the Email column - // of the corresponding row in Table2 - base.utils.lookupAndCopy('Table2', 'Email', 'Name', 'Table1', 'Email'); - - // Match the rows with the same content in the Name column in Table1 and the Name1 column - // in Table2, and copy the contents of the Email column of the row in Table1 to the - // Email1 column of the corresponding row in Table2 - base.utils.lookupAndCopy('Table2', 'Email1', 'Name1', 'Table1', 'Email', 'Name'); - - ``` - -!!! abstract "query" - - Filter and summarize the table `tableName` data of the view `viewName` by SQL like `query` statements. - - ``` js - base.utils.query(tableName: String, viewName: String, query: String); - ``` - - __Example__ - - ``` js - // Filter out the rows where the sum of the three columns 'number', 'number1', - // and 'number2' is greater than 5 then sum the number and number2 columns in these rows, - // return {number: 12, number2: 23} - base.utils.query('Table1', 'Default View', 'select sum(number), sum(number2) where number + number1 + number2 > 5'); - ``` diff --git a/docs/scripts/javascript/objects/views.md b/docs/scripts/javascript/objects/views.md deleted file mode 100644 index 75bef272..00000000 --- a/docs/scripts/javascript/objects/views.md +++ /dev/null @@ -1,143 +0,0 @@ -# Views - -You'll find below all the available methods to interact with the views of a SeaTable table. - -{% - include-markdown "includes.md" - start="" - end="" -%} - -## Get View(s) - -!!! abstract "getActiveView" - - Get the current view of the active table. - - ``` js - base.getActiveView(); - ``` - - __Output__ Single view object - - __Example__ - ``` js - const view = base.getActiveView(); - output.text(view._id); - output.text(view); - ``` - -!!! abstract "getViewByName" - - Get a view of a particular `table`, specified by its name `viewName`. - - ``` js - base.getViewByName(table: Object/String/* (1)! */, viewName: String); - ``` - - 1. `table`: either a table object or the table name - - __Output__ Single view object (`undefined` if no view called `viewName` exists, throws an error if `table` doesn't exist) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - const view = base.getViewByName(table, 'Default View'); - output.text(view.name); - ``` - - ``` js - const view = base.getViewByName('Table1', 'Default View'); - output.text(view.name); - ``` - -!!! abstract "listViews / getViews (deprecated)" - - Get all the views of the `table`. - - ``` js - base.listViews(table: Object/String/* (1)! */); - ``` - - 1. `table`: either a table object or the table name - - __Output__ Array of view objects (throws an error if `table` doesn't exist) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - const views = base.listViews(table); - output.text(views.length); - ``` - -## Add View - -!!! abstract "addView" - - Add a view named `viewName` to a `table`. - - ``` js - base.addView(table: Object/String/* (1)! */, viewName: String); - ``` - - 1. `table`: either a table object or the table name - - __Output__ Nothing (throws an error if `table` doesn't exist) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - base.addView(table, 'view 2'); - ``` - - ``` js - base.addView('Table1', 'view 2'); - ``` - -## Rename View - -!!! abstract "renameView" - - Rename a view in the `table` specified by its current name `currentViewName` and its new name `nextViewName`. Please ensure that you choose a `nextViewName` that doesn't already exists in your `table`. - - ``` js - base.renameView(table: Object/String/* (1)! */, currentViewName: String, nextViewName: String); - ``` - - 1. `table`: either a table object or the table name - - __Output__ Nothing (throws an error if `table` or `currentViewName` doesn't exist) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - base.renameView(table, 'Default View', 'view2'); - ``` - - ``` js - base.renameView('Table1', 'Default View', 'view2'); - ``` - -## Delete View - -!!! abstract "deleteView" - - Delete a view in a particular `table`, specified by its name `viewName`. Deleting the last view is not possible. - - ``` js - base.deleteView(table: Object/String/* (1)! */, viewName: String); - ``` - - 1. `table`: either a table object or the table name - - __Output__ Nothing (throws an error if `table` doesn't exist or no view called `viewName` exists) - - __Example__ - ``` js - const table = base.getTableByName('Table1'); - base.deleteView(table, 'view2'); - ``` - - ``` js - base.deleteView('Table1', 'view2'); - ``` diff --git a/docs/scripts/python/common_questions.md b/docs/scripts/python/common_questions.md deleted file mode 100644 index 3d9bdbae..00000000 --- a/docs/scripts/python/common_questions.md +++ /dev/null @@ -1,139 +0,0 @@ -# Common questions (Python) - -??? question "How to make the script support both local and cloud run" - - ## How to make the script support both local and cloud run - - __Flexible authorization__ - - When the script runs in the cloud, it will provide a context object, which contains the server URL auto generated by the system and the API token of base. If you run the script in local, you need to manually specify these two variables; the API token can be generated in the drop-down menu "Advanced -> API Token" of the table. - - Use the following method to make the script support both local and cloud run - - ``` - from seatable_api import Base, context - - server_url = context.server_url or 'https://cloud.seatable.io' - api_token = context.api_token or 'c3c75dca2c369848455a39f4436147639cf02b2d' - - - base = Base(api_token, server_url) - base.auth() - ``` - - __Dependencies that need to be installed to run the script local__ - - The script need to install `seatable-api` when run in local. - - ``` - pip3 install seatable-api - ``` - - Additional requirements are: - - - Python >= 3.5 - - requests - - socketIO-client-nexus - -??? question "List of libraries supported in the cloud environment" - - ## List of libraries supported in the cloud environment - - In the cloud environment, Python scripts run within a Docker container. This container comes pre-configured with a set of Python libraries that can be imported and used in your scripts. If you require libraries not included in this set, please contact our support team. Otherwise, scripts using unsupported libraries can only be executed locally. - - __Python Standard Library__ - - The cloud environment currently utilizes **Python 3.12**. This version supports all modules in the Python 3.12 standard library. Common built-in libraries such as `os`, `sys`, `datetime`, and others are readily available for use in your scripts. - - __Third-Party Libraries__ - - In addition to the standard library, we've included several popular third-party packages to enhance your scripting capabilities: - - - [seatable-api](https://pypi.org/project/seatable-api/): Official SeaTable Python API - - [dateutils](https://pypi.org/project/dateutils/): Extensions to Python's datetime module - - [requests](https://pypi.org/project/requests/): HTTP library for Python - - [pyOpenSSL](https://pypi.org/project/pyOpenSSL/): Python wrapper for OpenSSL - - [Pillow](https://pypi.org/project/Pillow/): Python Imaging Library (Fork) with support for [HEIF images](https://pypi.org/project/pillow-heif/) - - [python-barcode](https://pypi.org/project/python-barcode/): Barcode generator - - [qrcode](https://pypi.org/project/qrcode/): QR Code generator - - [pandas](https://pypi.org/project/pandas/): Data manipulation and analysis library - - [numpy](https://pypi.org/project/numpy/): Fundamental package for scientific computing - - [openai](https://pypi.org/project/openai/): OpenAI API client library - - [ldap3](https://pypi.org/project/ldap3/): LDAP v3 client library - - [pydantic](https://pypi.org/project/pydantic/): Data validation and settings management using Python type annotations - - [httpx](https://pypi.org/project/httpx/): A next-generation HTTP client for Python - - [PyJWT](https://pypi.org/project/PyJWT/): JSON Web Token implementation in Python - - [python-socketio](https://pypi.org/project/python-socketio/): Python implementation of the Socket.IO realtime server - - [scipy](https://pypi.org/project/scipy/): Fundamental algorithms for scientific computing in Python - - [PyPDF](https://pypi.org/project/pypdf/): PDF toolkit for Python - - [pdfmerge](https://pypi.org/project/pdfmerge/): Merge PDF files - - [Markdown](https://pypi.org/project/Markdown/): Convert Markdown to HTML - - [RapidFuzz](https://pypi.org/project/RapidFuzz/): A fast string matching library using string similarity calculations - - This list is not exhaustive. For a complete, up-to-date list of available third-party packages, you can run the following Python script in your SeaTable environment: - - ```python - import importlib.metadata - - # List all installed packages - installed_packages = importlib.metadata.distributions() - - # Print package names - for package in installed_packages: - print(package.metadata['Name']) - ``` - -??? question "Install and use custom python libraries" - - ## Install and use custom python libraries - - - The python libraries in SeaTable Cloud can not be changed. - - If you run your own SeaTable Server it is possible to install your own libraries. - -??? question "Printing complex elements (dicts, tables, arrays of rows) is sometimes difficult to read" - - ## Printing complex elements is sometimes difficult to read - - Do not hesitate to run your code in a Python IDE which could have specific features for data visualization (don't forget you won't be able to rely on context to provide `api_token` and `server_url`, see first question for dual run syntax). You could also use the `json` library to make the output of complex objects easier to read: - - ```python - import json # (1)! - from seatable_api import Base, context - base = Base(context.api_token, context.server_url) - base.auth() - - print(json.dumps(base.get_metadata(), indent=' ')) # (2)! - ``` - - 1. Import the json library - - 2. Print `json.dumps(object, indent='  ')` instead of just printing object. You have to explicitly specify the indent character (which is not a classic space character) as the output window of SeaTable's script editor actually trims indent spaces. - -??? question "How to deal with more than 1000 rows at once with batch operations?" - - ## Dealing with more than 1000 rows at once with batch operations - - As presented in the [API Reference](https://api.seatable.com/reference/limits), batch operations such as `base.batch_append_rows`, `base.batch_update_rows`, `base.batch_delete_rows` or `base.batch_update_links` have a maximum number of 1000 rows. To deal with a higher number of rows, you could: - - - Use an `INSERT`, `UPDATE` or `DELETE` [SQL query](/scripts/sql/introduction.md#supported-sql-syntax) that can operate on an unlimited number of rows - - - Use a `while` loop to split your operation into 1000-rows chunks for example (however this won't exactly be a single operation anymore): - - ```python - from seatable_api import Base, context - - base = Base(context.api_token, context.server_url) - base.auth() - - # You want to batch append new_rows which is more than 1000-rows long - while len(new_rows)>0 : - end = min(1000, len(new_rows)) - rows_chunk = new_rows[:end] - print(f"{rows_chunk[0]['Name']} > {rows_chunk[-1]['Name']}") - base.batch_append_rows("Table1", rows_chunk) - new_rows = new_rows[end:len(new_rows)] - ``` - - To [batch update links](./objects/links.md#update-links), the loop will be slightly more complex as you'll have to deal with `other_rows_ids_map` as well - - diff --git a/docs/scripts/python/examples/auto-add-rows.md b/docs/scripts/python/examples/auto-add-rows.md deleted file mode 100644 index b71b1250..00000000 --- a/docs/scripts/python/examples/auto-add-rows.md +++ /dev/null @@ -1,62 +0,0 @@ -{% - include-markdown "includes.md" - start="" - end="" -%} - -Unlike JavaScript, Python scripts allow you to handle single- or multiple-select options, which make you capable of checking if the needed options exist and of creating them if necessary directly inside the script. - -```python -from seatable_api import Base, context -from seatable_api.date_utils import dateutils -""" -This script add two expenses rows in a ledger. Before adding them, -it checks if they have already been added for the current month. -""" - -base = Base(context.api_token, context.server_url) -base.auth() - -# Get date objects on the 10th and 20th of the current month -date = dateutils.today() -date10 = dateutils.date(dateutils.year(date), dateutils.month(date), 10) -date20 = dateutils.date(dateutils.year(date), dateutils.month(date), 20) - -# Check if the options you will need already exist, and create them if necessary -options_to_add = [] -current_options = base.get_column_by_name('Daily expenses', 'Type (single select)')['data']['options'] -cloud_service_option = [o for o in current_options if o['name'] == 'Cloud service'] -if not cloud_service_option : - options_to_add.append({"name": "Cloud service", "color": "#aaa", "textColor": "#000000"}) -daily_office_option = [o for o in current_options if o['name'] == 'Daily office'] -if not daily_office_option : - options_to_add.append({"name": "daily office", "color": "#aaa", "textColor": "#000000"}) -if options_to_add : - base.add_column_options('Daily expenses', 'Type (single select)', options_to_add) - -# Check if the monthly expense items have already been created and eventually create them -feeAWS = {} -feeAWSCurrentMonth = base.query('select * from `Daily expenses` where Name="Amazon Cloud Service" and Date="' + date10 + '"') -if not feeAWSCurrentMonth : - feeAWS = {'Name': 'Amazon Cloud Service', - 'Date': date10, - 'Type': 'Cloud service', - 'Type (single select)': 'Cloud service', - } - -feeClean = {} -feeCleanCurrentMonth = base.query('select * from `Daily expenses` where Name="Clean" and Date ="' + date20 + '"') -if not feeCleanCurrentMonth : - feeClean = {'Name': 'Clean', - 'Date': date20, - 'Type': 'Daily office', - 'Type (single select)': 'Daily office', - 'Fee': 260 - } - -# Create the monthly expense items (if needed) -if (feeAWS) : - base.append_row('Daily expenses', feeAWS); -if (feeClean) : - base.append_row('Daily expenses', feeClean); -``` \ No newline at end of file diff --git a/docs/scripts/python/examples/calculate-accumulated-value.md b/docs/scripts/python/examples/calculate-accumulated-value.md deleted file mode 100644 index 2ac20a0e..00000000 --- a/docs/scripts/python/examples/calculate-accumulated-value.md +++ /dev/null @@ -1,65 +0,0 @@ -{% - include-markdown "includes.md" - start="" - end="" -%} - -```python -from seatable_api import Base, context -from seatable_api.date_utils import dateutils -""" -This script accumulates the values of the current row and the previous rows, -and records the result to the current row (as the *Calculate accumulated value* -operation from the data processing menu). -""" - -base = Base(context.api_token, context.server_url) -base.auth() - -table_name = 'Accumulated value' -view_name = 'Default View' - -# Name of the column that records total number at a specific time -value_column_name = 'Value to add' -# Name of the column that need to calculate incremental value -incremental_column_name = 'Incremental total' - -view = base.get_view_by_name(table_name, view_name) -rows = base.list_rows(table_name, view_name) - -# If current view is a grouped view -if 'groupbys' in view and len(view['groupbys']) > 0 : -# # Get group view rows - grouping_column = [c for c in base.list_columns(table_name) if 'column_key' in view['groupbys'][0] and c['key'] == view['groupbys'][0]['column_key']] - if grouping_column and len(grouping_column) == 1 : - grouping_column_name = grouping_column[0]['name'] - group_values = [] - for row in rows : - if row[grouping_column_name] not in group_values : - group_values.append(row[grouping_column_name]) - for value in group_values : - group_rows = [r for r in rows if r[grouping_column_name] == value] - incremental_total = 0 - for row_index, row in enumerate(group_rows) : - current_number = row[value_column_name]; - if current_number : - # Calculate increment - # If there is no previous row, set increase_count to 0 - previous_number = 0 if row_index == 0 else incremental_total - increase_count = current_number + previous_number - incremental_total = increase_count - # Set calculated increment to row - base.update_row(table_name, row['_id'], {incremental_column_name: increase_count}) -else : - incremental_total = 0 - for row_index, row in enumerate(rows) : - current_number = row[value_column_name]; - if current_number : - # Calculate increment - # If there is no previous row, set increase_count to 0 - previous_number = 0 if row_index == 0 else incremental_total - increase_count = current_number + previous_number - incremental_total = increase_count - # Set calculated increment to row - base.update_row(table_name, row['_id'], {incremental_column_name: increase_count}) -``` \ No newline at end of file diff --git a/docs/scripts/python/examples/compute-attendance-statistics.md b/docs/scripts/python/examples/compute-attendance-statistics.md deleted file mode 100644 index c3d20073..00000000 --- a/docs/scripts/python/examples/compute-attendance-statistics.md +++ /dev/null @@ -1,83 +0,0 @@ -{% - include-markdown "includes.md" - start="" - end="" -%} - -```python -from seatable_api import Base, context -""" -This script computes, from a list of clocking times, -daily clock in (earliest clocking) and clock out -(latest clocking) times for each day and staff member. -""" - -base = Base(context.api_token, context.server_url) -base.auth() - -origin_table_name = 'Clocking table' -origin_view_name = 'Default View' -origin_name_column_name = 'Name' -origin_department_column_name = 'Department' -origin_date_column_name = 'Date' -origin_time_column_name = 'Clocking time' - -target_table_name = 'Attendance statistics' -target_name_column_name = 'Name' -target_department_column_name = 'Department' -target_date_column_name = 'Date' -target_start_time_column_name = 'Clock-in' -target_end_time_column_name = 'Clock-out' - -def get_date(e): - return e[origin_date_column_name] - -#table = base.getTableByName(origin_table_name) -#view = base.getViewByName(table, origin_view_name) -rows = base.list_rows(origin_table_name, origin_view_name) - -# Sort the rows in the Clocking table according to the date column -rows.sort(key=get_date) - -# Group all rows via date and save them to groupedRows, the format -# of the object is {'2020-09-01': [row, ...], '2020-09-02': [row, ...]} -grouped_rows = {} -date_stat_items = [] -for row in rows : - date = row[origin_date_column_name] - if date not in grouped_rows : - grouped_rows[date] = [] - grouped_rows[date].append(row) - -# Traverse all the groups in grouped_rows -for date_key in grouped_rows : - # Get all clocking data of all members for the current date - date_rows = grouped_rows[date_key] - staff_date_stat_item = {} - # Traverse these rows and group by the name of the employee, get the clock-in and clock-out time of each employee that day, and save it to staffDateStatItem - # the format is { EmployeeName: {Name: 'EmployeeName', Date: '2020-09-01', Clock-in: '08:00', Clock-out: '18:00'},... } - for row in date_rows : - name = row[origin_name_column_name] - if name not in staff_date_stat_item : - # Generate a new row based on the original row data, and add Clock-in and Clock-out columns in the newly generated row - staff_date_stat_item[name] = { - target_name_column_name: name, - target_date_column_name: row[origin_date_column_name], - target_department_column_name: row[origin_department_column_name], - target_end_time_column_name: row[origin_time_column_name], - target_start_time_column_name: row[origin_time_column_name] - } - else : - # When another record (same employee and same date) is found, compare the time, choose the latest one as the Clock-out time, and the earliest one as the Clock-in time - time = row[origin_time_column_name] - staff_item = staff_date_stat_item[name] - if staff_item[target_start_time_column_name] > time : - staff_item[target_start_time_column_name] = time - elif staff_item[target_end_time_column_name] < time : - staff_item[target_end_time_column_name] = time - for staff in staff_date_stat_item : - date_stat_items.append(staff_date_stat_item[staff]) - -# Write the attendance data of all employees on the current date into the table -base.batch_append_rows(target_table_name, date_stat_items) -``` \ No newline at end of file diff --git a/docs/scripts/python/examples/generate_barcode.md b/docs/scripts/python/examples/generate_barcode.md deleted file mode 100644 index f95e988a..00000000 --- a/docs/scripts/python/examples/generate_barcode.md +++ /dev/null @@ -1,90 +0,0 @@ -# Generate Barcode - -This Python script demonstrates the process of converting text slices into barcode images using the `barcode` module and storing them in an image column within SeaTable. It offers an automated way to generate barcode images from text data in a SeaTable table, enhancing data visualization and association within the SeaTable platform. - -Here is the structure of the table named `Generate 1 or 2D barcodes` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): - -| Column name | Message | Barcode image | -| ----------- |:------:|:------:| -| **Column type** | text | image | - -This table can be shared with the [Generate QR code example](./generate_qrcode.md) by adding it an extra *QRcode image* image-type column. - -## Process Overview - -1. **Iterates through rows** in a SeaTable table whose name is specified in the `TABLE_NAME` variable and check if a barcode already exists for each row (operates only on rows without barcode). Includes exception handling to manage errors encountered during the barcode image generation process. -2. **Converts text data** from a designated column (`TEXT_COL`) into barcode images using the specified barcode type (`BARCODE_TYPE`). -3. **Saves the generated barcode images** temporarily. -4. **Uploads the generated barcode images** to SeaTable and associates them with corresponding records (in the `BARCODE_IMAGE_COL` column). -5. **Removes temporary barcode image files** after successful upload. - -## Code - -```python -import os -import time -import barcode -from barcode.writer import ImageWriter -from seatable_api import Base, context -""" -The python script shows how to transfer a slice of text into a barcode image and save it into -the image column -""" - -api_token = context.api_token or "859ad340d9a2b...8992e14853af5" -server_url = context.server_url or "https://cloud.seatable.io" - -TABLE_NAME = 'Generate 1 or 2D barcodes' -TEXT_COL = "Message" # column which is expected to be transferred into barcode -BARCODE_IMAGE_COL = "Barcode image" -BARCODE_TYPE = 'code128' - -CUSTOM_OPTIONS = { - "module_width": 0.2, # width of single stripe of barcode, mm - "module_height": 30.0, # height of barcode, mm - "quiet_zone": 6.5, # padding size of first and last stripe to the image, mm - "font_size": 10, # font size of the text below the barcode, pt - "text_distance": 5.0, # distance between the text and the barcode, mm -} - - -CODE = barcode.get_barcode_class(BARCODE_TYPE) -base = Base(api_token, server_url) -base.auth() - -def get_time_stamp(): - return str(int(time.time()*100000)) - -updated_rows = 0 -# 1. Iterate through rows -for row in base.list_rows(TABLE_NAME): - # 1.b Continue if the image is already shown up here - if row.get(BARCODE_IMAGE_COL): - continue - # 1.c Error handling - try: - row_id = row.get('_id') - msg = str(row.get(TEXT_COL)) - - # 2. Create a barcode object - code_img = CODE(msg, writer=ImageWriter()) - - # 3. Temporarily save the image - save_name = "%s_%s" % (row_id, get_time_stamp()) - file_name = code_img.save("/tmp/%s" % save_name, options=CUSTOM_OPTIONS) - - # 4. Upload the barcode image to the base and associate it to the row - info_dict = base.upload_local_file(file_name, name=None, file_type='image', replace=True) - img_url = info_dict.get('url') - base.update_row(TABLE_NAME, row_id, {BARCODE_IMAGE_COL: [img_url]}) - - # 5. Remove the image file which was saved temporarily - os.remove(file_name) - updated_rows += 1 - except Exception as error: - print("error occurred during barcode generate", error) - continue - -# Summary -print("I created %s barcodes" % updated_rows) -``` diff --git a/docs/scripts/python/examples/generate_qrcode.md b/docs/scripts/python/examples/generate_qrcode.md deleted file mode 100644 index c3dddafb..00000000 --- a/docs/scripts/python/examples/generate_qrcode.md +++ /dev/null @@ -1,97 +0,0 @@ -# Generate QR code - -This Python script is designed to generate QR codes and associate them with corresponding records in a SeaTable base. In addition to `seatable_api` library, it uses the `qrcode` module to accomplish this task. In comparison to the [Generate barcode example](./generate_barcode.md), this example adds an `OVERWRITE` parameter to choose if existing QRcodes should be recreated or not. - -Here is the structure of the table named `Generate 1 or 2D barcodes` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): - -| Column name | Message | QRcode image | -| ----------- |:------:|:------:| -| **Column type** | text | image | - -This table can be shared with the [Generate barcode example](./generate_barcode.md) by adding it an extra *Barcode image* image-type column. - -## Process Overview - -1. **Iterates through rows** in a SeaTable table whose name is specified in the `TABLE_NAME` variable and check if a QRcode already exists for each row (operates either on all rows or only on rows without QRcodes depending on the `OVERWRITE` parameter). Includes exception handling to manage errors encountered during the barcode image generation process. -2. **Generates QR codes** based on the text content in the designated column (`TEXT_COL`). -3. **Saves the QR code images** temporarily. -4. **Uploads the generated images** to SeaTable and associates them with corresponding records (in the `QRCODE_IMAGE_COL` column). -5. **Removes temporary image files** after successful upload. - -## Code - -```python -import os -import time -import qrcode -from seatable_api import Base, context -""" -The python script shows how to transfer a slice of text into a QR code image and save it into -the image column -""" - -api_token = context.api_token or "859ad340d9a2b...8992e14853af5" -server_url = context.server_url or "https://cloud.seatable.io" - -TABLE_NAME = "Generate 1 or 2D barcodes" -TEXT_COL = "Message" # text column which is expected to be transferred into QR code -QRCODE_IMAGE_COL = "QR code image column" - -OVERWRITE = True # set to True to overwrite existing barcode images - -base = Base(api_token, server_url) -base.auth() - -qr = qrcode.QRCode( - version=2, - error_correction=qrcode.constants.ERROR_CORRECT_L, - box_size=40, - border=8 -) - -def get_time_stamp(): - return str(int(time.time() * 100000)) - -def main(): - # 1. Iterate through rows - for row in base.list_rows(TABLE_NAME): - # 1.b Continue if the image is already shown up here - # and OVERWRITE parameter is not True - if not OVERWRITE and row.get(QRCODE_IMAGE_COL): - print("Skipping row. Image already exists.") - continue - # 1.c Error handling - try: - row_id = row.get('_id') - message = row.get(TEXT_COL) - - # Check if message isn't empty before processing - if not message: - print("Skipping row. Empty message.") - continue - - # 2. Clear, add data and make a QRCode object - qr.clear() - qr.add_data(str(message)) - qr.make() - - img = qr.make_image(fill_color="black", back_color="white") - - # 3. Temporarily save the image - save_name = f"{row_id}_{get_time_stamp()}" - img.save(f"/tmp/{save_name}.png") - - # 4. Upload the QR code image to the base and associate it to the row - info_dict = base.upload_local_file(f"/tmp/{save_name}.png", name=None, file_type='image', replace=True) - img_url = info_dict.get('url') - base.update_row(TABLE_NAME, row_id, {QRCODE_IMAGE_COL: [img_url]}) - - # 4. Remove the image file which was saved temporarily - os.remove(f"/tmp/{save_name}.png") - except Exception as exception: - print("Error occurred during Image generation:", exception) - continue - -if __name__ == "__main__": - main() -``` diff --git a/docs/scripts/python/examples/heic_to_png.md b/docs/scripts/python/examples/heic_to_png.md deleted file mode 100644 index dd7dad1d..00000000 --- a/docs/scripts/python/examples/heic_to_png.md +++ /dev/null @@ -1,73 +0,0 @@ -# Convert HEIC to PNG - -!!! warning "Requires Python Runner v4.1.1" - - The library `pillow_heif` was added with the Python Runner version 4.1.1. If you're using SeaTable Cloud, this was added with v5.1. - -This Python script demonstrates how to convert HEIC image files to PNG format and save the converted file into a new row in a SeaTable base. It uses the `pillow_heif` library to handle HEIC files, `Pillow` for image processing, and the `seatable_api` library to interact with SeaTable. The script processes **one HEIC file per row**; if you need to handle multiple HEIC files per row, you'll need to modify the script accordingly. - -Here is the structure of the table named `Convert images` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): - -| Column name | HEIC | PNG | -| ----------- |:------:|:------:| -| **Column type** | image | image | - -## Script Overview - -The script performs the following steps: - -1. **Authenticate with SeaTable:** Uses the API token and server URL to authenticate. -2. **Download HEIC Files:** Retrieves HEIC files from the `HEIC` column in SeaTable. -3. **Convert HEIC to PNG:** Transforms the downloaded HEIC file to PNG format with 90% quality using `Pillow`. -4. **Upload Converted PNG:** (a) Uploads the PNG file back to SeaTable and (b) updates the row with the new file URL in the `PNG` column. - -## Example Script - -```python -import requests -from PIL import Image -from pillow_heif import register_heif_opener -from seatable_api import Base, context -""" -This Python script demonstrates how to convert HEIC image files - to PNG format and save the converted file into a new row in a SeaTable base. -""" - -# Activate heif/heic support -register_heif_opener() # (1)! - -TABLE_NAME = "Convert images" -FILE_COLUMN = "HEIC" -RESULT_COLUMN = "PNG" - -# 1. Authentication -base = Base(context.api_token, context.server_url) -base.auth() - -for row in base.list_rows(TABLE_NAME): - if row.get(FILE_COLUMN) is None: - continue - - # 2. Download heic image - url = row.get(FILE_COLUMN)[0] - filename_heic = url.split('/')[-1] - base.download_file(url, filename_heic) - - # 3. Transform image to png - im = Image.open(filename_heic) - filename_png = f'image-{row["_id"]}.png' - im.save(filename_png, quality=90) - print('Saved image') - - # 4.a) Upload - info_dict = base.upload_local_file(filename_png, name=None, file_type='image', replace=True) - print('Uploaded file') - - # 4.b) Save back to SeaTable Base - img_url = info_dict.get('url') - base.update_row(TABLE_NAME, row['_id'], {RESULT_COLUMN: [img_url]}) - print('Stored image info in base') -``` - -1. Note the `register_heif_opener()` call to enable HEIC file support. - diff --git a/docs/scripts/python/examples/index.md b/docs/scripts/python/examples/index.md deleted file mode 100644 index 0eabe34b..00000000 --- a/docs/scripts/python/examples/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# Examples - -This section contains some examples of Python Scripts. The **first three scripts** are the same as in the JavaScript section. - -Even if Python scripts are capable of checking if the base structure (tables and columns) needed exist and of creating it if necessary, we didn't implement this feature in the scripts so you can focus on the actual goal of each script. - -{% - include-markdown "includes.md" - start="" - end="" -%} - -# Add rows - -This script demonstrates how to add rows to record monthly repetitive expenses in a ledger. - -[read more :material-arrow-right-thin:](/scripts/python/examples/auto-add-rows/) - -## Calculate accumulated value - -This script computes an accumulated value (adds the value of the current row and the previous rows), similar to the *Calculate accumulated value* operation from the data processing menu. - -[read more :material-arrow-right-thin:](/scripts/python/examples/calculate-accumulated-value/) - -## Statistics - -This script computes, from a list of clocking times, daily clock in (earliest clocking) and clock out (latest clocking) times for each day and staff member. - -[read more :material-arrow-right-thin:](/scripts/python/examples/compute-attendance-statistics/) - -## Email sender - -This Python script demonstrates sending emails via SMTP using the smtplib module, constructing MIME objects to compose rich content emails within SeaTable and creating HTML content from a "long text"-type column using the markdown module. - -[read more :material-arrow-right-thin:](/scripts/python/examples/send_email/) - -## Barcode generator - -This Python script demonstrates the process of converting text slices into barcode images and storing them in an image column within SeaTable. - -[read more :material-arrow-right-thin:](/scripts/python/examples/generate_barcode/) - -## QR code generator - -This Python script is designed to generate QR codes and associate them with corresponding records in a SeaTable base. It uses the seatable_api library and qrcode library to accomplish this task. - -[read more :material-arrow-right-thin:](/scripts/python/examples/generate_qrcode/) - -## MySQL synchronization - -This Python script facilitates the synchronization of data from a MySQL database to a SeaTable table. - -[read more :material-arrow-right-thin:](/scripts/python/examples/sync_mysql/) - -## Watch stock price - -Integrating data from the Twelve Data API with SeaTable facilitates the updating and maintenance of current stock prices within a designated table in the SeaTable environment. - -[read more :material-arrow-right-thin:](/scripts/python/examples/update_stock_price/) - -## Merge PDF - -Merge PDF files and save the merged file into a new row in a SeaTable base. - -[read more: :material-arrow-right-thin:](/scripts/python/examples/merge_pdf/) - -## Convert HEIC to PNG - -Convert HEIC image files to PNG format and save the converted file into a new row in a SeaTable base. - -[read more: :material-arrow-right-thin:](/scripts/python/examples/heic_to_png/) diff --git a/docs/scripts/python/examples/merge_pdf.md b/docs/scripts/python/examples/merge_pdf.md deleted file mode 100644 index 2d12312a..00000000 --- a/docs/scripts/python/examples/merge_pdf.md +++ /dev/null @@ -1,73 +0,0 @@ -# Merge PDF - -!!! warning "Requires Python Runner v4.1.1" - - The library `pdfmerge` was added with the Python Runner version 4.1.1. If you're using SeaTable Cloud, this was added with v5.1. - -This Python script demonstrates how to merge several PDF files and save the merged file into a new column in a SeaTable base. It utilizes the `pdfmerge` library to handle the PDF merging process and the `seatable_api` library to interact with SeaTable. - -Here is the structure of the table named `Merge PDF` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): - -| Column name | PDF files | Merged file | -| ----------- |:------:|:------:| -| **Column type** | file | file | - -## Script Overview - -The script performs the following steps: - -1. **Authenticate with SeaTable:** Uses the API token and server URL to authenticate. -2. **Retrieve the files:** For each row, the script gets the name and URL of every file in the `PDF files` column. -3. **Download PDF Files** -4. **Merge PDFs:** Combines the downloaded PDF files using `pdfmerge` into a single PDF named with the pattern `output-{row_id}.pdf`. -5. **Upload Merged PDF:** Uploads the merged PDF back to SeaTable and updates the row with the new file in the `Merged file` column. - -## Example Script - -```python -import os -import requests -import sys -import shutil -from pdfmerge import pdfmerge -from seatable_api import Base, context -""" -This Python script demonstrates how to merge PDF -files and save the merged file into a new column. -""" - -TABLE_NAME = "Merge PDF" -FILE_COLUMN = "PDF files" -RESULT_COLUMN = "Merged file" - -# 1. Authentication -base = Base(context.api_token, context.server_url) -base.auth() - -# Get rows -for row in base.list_rows(TABLE_NAME): - if row.get(FILE_COLUMN) is None: - continue - - # 2. Retrieve all files from the row - files = [{'name': file['name'], 'URL': file['url']} for file in row[FILE_COLUMN]] - file_names = [] - - # 3. Download PDFs - for f in files : - base.download_file(f['URL'],f['name']) - file_names.append(f['name']) - assert len(file_names) == len(files) - print(f"Downloaded {len(files)} files") - - # 4. Merge - output_filename = f'output-{row["_id"]}.pdf' - pdfmerge(file_names, output_filename) - print('Merged PDF files') - - # 5. Upload file + store URL in the base - info_dict = base.upload_local_file(output_filename, name=None, file_type='file', replace=True) - print(info_dict) - base.update_row(TABLE_NAME, row['_id'], {RESULT_COLUMN: [info_dict]}) - print('Uploaded PDF file') -``` diff --git a/docs/scripts/python/examples/send_email.md b/docs/scripts/python/examples/send_email.md deleted file mode 100644 index d30af524..00000000 --- a/docs/scripts/python/examples/send_email.md +++ /dev/null @@ -1,194 +0,0 @@ -# Send emails - -This Python script demonstrates sending emails via SMTP using the [smtplib module](https://docs.python.org/3/library/smtplib.html), constructing MIME objects to compose rich content emails within SeaTable and creating HTML content from a "long text"-type column using the markdown module. It also retrieves configuration parameters from the database. This example uses two tables: - -- The `Contacts` table storing the contacts you want to send email to: - -| Column name | Name | Email | -| ----------- |:------:|:------:| -| **Column type** | text | email | - -- The `Send email config` table storing the email sending parameters: - -| Column name | Subject | Recipient email | Subject source| Email format | Attach file | File | -| ----------- |:-----:|:-------------:|:-----------:|:----------:|:---------:|:--:| -| **Column type** | text | single select | single select | single select | checkbox | file | - -- Recipient email can be `hard-coded` (recipients are defined l.48 of the script as a list of email addresses) or `database` (recipients are retrieved from the `Email` column of the `Contacts` table). -- `Subject source` can be `hard-coded` (define manually the subject of the mail l.57 of the script) or `database` (the subject is retrieved from the `Subject` column of the `Send email config` table). -- Email format can be `text` (plain text defined l.71), `html` (HTML-formatted message, defined l.77) or `database` (the email body retrieved from the `Email body` column of the `Send email config` table). -- If `Attach file` is checked, the first file from the `File` column will be enclosed (don't forget to adapt the `_subtype` l.115 of the script if your file is not a pdf). -- You can eventually add a `Send email` column, configured to launch the script. The script itself is written to use either the `context.current_row` data, or the first row of the table if no `context` is defined (script launched from outside SeaTable). - -## Process overview - -1. **Retrieves email configuration** from the `Send email config` table. -2. Eventually **retrieves recipient email addresses** from a designated SeaTable table column (`Email` column in `Contact` table). -3. Eventually **retrieves email subject**. -3. Eventually **retrieves email body**. -4. **Composes an email** using plain text or HTML content to create a rich-text message body. -5. **Attaches a file** from SeaTable to the email by fetching its download link using the SeaTable API and attaching it to the email. -6. **Sends the email** after authenticating using SMTP parameters. - -## Code - -```python linenums="1" -import markdown -import smtplib, ssl -from email.mime.application import MIMEApplication -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from email.header import Header -from urllib import parse -import requests -from seatable_api import Base, context -""" -This Python script demonstrates sending emails via SMTP -using the smtplib module and constructing MIME objects -to compose rich content emails within SeaTable. -""" - -# SeaTable API authentication -base = Base(context.api_token, context.server_url) -base.auth() - -CONFIG_TABLE = 'Send email config' -CONTACTS_TABLE = 'Contacts' - -# SMTP server configurations for sending emails -SMTP_SERVER = 'my.smtpserver.com' -SMTP_PORT = 465 -USERNAME = 'my.em@il.com' -PASSWORD = 'topsecret' -SENDER = 'My name' - -# 1. Get email configuration from the 'Send email config' table -current_row = context.current_row or base.list_rows(CONFIG_TABLE)[0] -# Choose RECIPIENT_EMAIL between "hard-coded" (addresses l.48 of this script) -# or "database" (get emails from 'Email' column in the 'Contacts' table) -RECIPIENT_EMAIL = current_row.get('Recipient email') -# Choose SUBJECT between "hard-coded" (subject l.57 of this script) -# or "database" (get subject from 'Subject' column in the 'Send email config' table) -SUBJECT_SOURCE = current_row.get('Subject source') -# Choose EMAIL_FORMAT between "text" (hard-coded plain text, defined l.71), -# "html" (hard-coded HTML, defined l.77) -# and "database" (content of the 'Email body' column in the 'Send email config' table) -EMAIL_FORMAT = current_row.get('Email format') -# If Attach file, the script retrieves the first file from the 'File' column of the 'Sending email config' -ATTACH_FILE = current_row.get('Attach file') - -# 2. Set recipient email addresses -if RECIPIENT_EMAIL == "hard-coded" : - # Option a) Define the recipient email address in this script - receivers = ['johndoe@email.com'] -elif RECIPIENT_EMAIL == "database" : - # Option b) Retrieve recipient email addresses from the 'Contacts' table in SeaTable - receiver_rows = base.list_rows(CONTACTS_TABLE) - receivers = [row['Email'] for row in receiver_rows if row.get('Email')] - -# 3. Set email subject -if SUBJECT_SOURCE == "hard-coded" : - # Option a) Define the subject in this script - subject = 'SeaTable Send email' -elif SUBJECT_SOURCE == "database" : - # Option b) Retrieve the subject from the 'Send email config' table - current_row = context.current_row or base.list_rows(CONFIG_TABLE)[0] - subject = current_row.get('Subject') - -# 4. Construct the email message -msg = MIMEMultipart() -msg['Subject'] = subject -msg['From'] = SENDER + '<' + USERNAME + '>' -msg['To'] = ", ".join(receivers) - -if EMAIL_FORMAT == "text" : - # Option a) plain text message - text = "Hi!\nHow are you?\nHere is the link you wanted:\nhttp://www.seatable.com" - text_plain = MIMEText(text,'plain', 'utf-8') - msg.attach(text_plain) - -elif EMAIL_FORMAT == "html" : - # Option b) HTML content for the email body - html = """ - - - -

Hi!
- This is a sample message from SeaTable -

- - - """ - text_html = MIMEText(html, 'html', 'utf-8') - msg.attach(text_html) - -elif EMAIL_FORMAT == "database" : - # Option c) HTML content for the email body from the Email body column - current_row = context.current_row or base.list_rows(CONFIG_TABLE)[0] - text_html = MIMEText(markdown.markdown(current_row['Email body']),'html', 'utf-8') - msg.attach(text_html) - -# 5. Attach a file from SeaTable to the email -if ATTACH_FILE : - # Get the file from the 'send email config' table - current_row = context.current_row or base.list_rows(CONFIG_TABLE)[0] - file_name = current_row['File'][0]['name'] - file_url = current_row['File'][0]['url'] - path = file_url[file_url.find('/files/'):] - download_link = base.get_file_download_link(parse.unquote(path)) - - try: - response = requests.get(download_link) - if response.status_code != 200: - print('Failed to download file, status code: ', response.status_code) - exit(1) - except Exception as e: - print(e) - exit(1) - - # Attach the file to the email (adapt _subtype to the type of your file) - attached_file = MIMEApplication(response.content, _subtype = "pdf") - attached_file.add_header('content-disposition', 'attachment', filename = file_name) - msg.attach(attached_file) - -# 6. Send the email - -# option a) Sending the email using SMTP -try: - with smtplib.SMTP() as email_server: - email_server.connect(SMTP_SERVER) - email_server.login(USERNAME, PASSWORD) - email_server.send_message(msg) - email_server.quit() -except smtplib.SMTPAuthenticationError: - print("SMTP User authentication error, Email not sent!") -except Exception as e: - print(f"SMTP exception {e}") - -''' -# option b) Sending the email using SMTP / SSL -ssl_context = ssl.create_default_context() -try: - with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, - context=ssl_context) as email_server: - email_server.login(USERNAME, PASSWORD) - email_server.send_message(msg) - email_server.quit() -except smtplib.SMTPAuthenticationError: - print("SMTP User authentication error, Email not sent!") -except Exception as e: - print(f"SMTP exception {e}") - -# option c) Sending the email using SMTP with STARTTLS -try: - with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as email_server: - email_server.starttls() - email_server.login(USERNAME, PASSWORD) - email_server.send_message(msg) - email_server.quit() -except smtplib.SMTPAuthenticationError: - print("SMTP User authentication error, Email not sent!") -except Exception as e: - print(f"SMTP exception {e}") -''' -``` diff --git a/docs/scripts/python/examples/sync_mysql.md b/docs/scripts/python/examples/sync_mysql.md deleted file mode 100644 index e394c1c6..00000000 --- a/docs/scripts/python/examples/sync_mysql.md +++ /dev/null @@ -1,76 +0,0 @@ -# SeaTable MySQL Synchronization - -This Python script facilitates the synchronization of data from a MySQL database to a SeaTable table, ensuring consistency and updating records seamlessly. Variables are present at the beginning of the script to easily adapt the names of both SeaTable and MySQL tables and columns. The `Sync MySQL` table requires a single `Name` text-type column for the script to be able to run. - -## Process Overview - -1. **Initializes connections** to both (a) SeaTable and (b) MySQL databases. -2. **Fetches existing data** from the `Name` column of the `Sync MySQL` SeaTable table. -3. **Retrieves data** from the MySQL `order` table. -4. **Compares MySQL data with SeaTable data** to identify new records by matching the `name` field. -5. **Adds new records** from MySQL to SeaTable (`Sync MySQL`) for synchronization. - - -## Code - -```python -import pymysql -from seatable_api import Base, context -""" -This Python script facilitates the synchronization of data -from a MySQL database to a SeaTable table, ensuring consistency -and updating records seamlessly. -""" - -# SeaTable base config -SERVER_URL = context.server_url or 'http://127.0.0.1:8000' -API_TOKEN = context.api_token or '...' - -# SeaTable table config -ST_TABLE_NAME = 'Sync MySQL' -ST_NAME_COLUMN = 'Name' - -# MySQL config -HOST = 'localhost' -USER = 'username' -PASSWORD = 'topsecret' -MYSQL_DB = 'seatable' -MYSQL_TABLE = 'order' -MYSQL_NAME_COLUMN = 'name' - -def sync_mysql(): - # 1. Initialize connection - # 1. a) SeaTable authentication - base = Base(API_TOKEN, SERVER_URL) - base.auth() - - # 1. b) MySQL connection - connection = pymysql.connect(host=HOST, user=USER, password=PASSWORD, db=MYSQL_DB) - - # 2. Fetch existing rows from seaTable - rows = base.list_rows(ST_TABLE_NAME) - row_keys = [row.get(ST_NAME_COLUMN) for row in rows] - - # 3. Retrieving data from MySQL - with connection.cursor(pymysql.cursors.DictCursor) as cursor: - sql = "SELECT * FROM " + MYSQL_TABLE - cursor.execute(sql) - mysql_data = cursor.fetchall() - - # Synchronization - rows_data = [] - for item in mysql_data: - # 4. Look for data from MySQL not present in SeaTable - if item.get(MYSQL_NAME_COLUMN) not in row_keys: - row_data = { - ST_NAME_COLUMN: item.get(MYSQL_NAME_COLUMN), - } - rows_data.append(row_data) - # 5. Eventually add missing records - if rows_data : - base.batch_append_rows(ST_TABLE_NAME, rows_data) - - -if __name__ == '__main__': - sync_mysql() -``` diff --git a/docs/scripts/python/examples/update_stock_price.md b/docs/scripts/python/examples/update_stock_price.md deleted file mode 100644 index f0e9a60d..00000000 --- a/docs/scripts/python/examples/update_stock_price.md +++ /dev/null @@ -1,75 +0,0 @@ -# Watch stock price by querying an API - -This Python script demonstrates how to retrieve data from an external source by making a `GET` request to an external API. The [Twelve Data](https://twelvedata.com) API is indeed used to update and maintain current stock prices within a designated SeaTable table. - -!!! info "Free subscription and fake/mock APIs" - A free subscription is available for Twelve Data if you just want to test the script (up to 800 calls per days are free). - - If you're interested in querying external APIs, you can find free playground APIs for such purpose such as the very specific [cat API](https://thecatapi.com/), [JSONPlaceholder](https://jsonplaceholder.typicode.com/) or more complex mock API such as [MockFast.io](https://mockfast.io/) allowing you to define the structure of the response for more complex and heavy testing. - -Here is the structure of the table named `Watch stock` you need so that this script could run (variables are present at the beginning of the script to easily adapt the names): - -| Column name | Symbol | Current stock price | -| ----------- |:------:|:------:| -| **Column type** | text | number (dollar) | - -You can create several lines to watch current stock price, for example by specifying *AAPL* or *AMZN* for the `Symbol` column. - -## Process Overview - -1. **Initializes configurations** for the Twelve Data API and SeaTable server. -2. **Fetches current stock prices** using the Twelve Data API based on stock symbols from a SeaTable table (from the `Symbol` column in the `Watch stock` table). -3. **Updates the SeaTable table** with the fetched current stock prices in the designated column (`Current stock price`). -4. **Displays the updated stock prices** for each symbol in the console. - -This script enables the automated update of current stock prices within a SeaTable table by leveraging data from the Twelve Data API, ensuring that stock information remains up-to-date within the SeaTable environment. - -## Code - -```python -from seatable_api import Base, context -import requests -""" -This Python script integrates data from the Twelve Data API with SeaTable -to update and maintain current stock prices. -""" - -# 1. Configuration variables for both SeaTable and Twelve Data -TWELVE_DATA_API_KEY = "dfb122bbca6a4..." # Replace this with your actual API key from Twelve Data - -SERVER_URL = context.server_url or "https://cloud.seatable.io/" -API_TOKEN = context.api_token or "..." - -TABLE_WITH_STOCK_SYMBOLS = "Stock watch" -COLUMN_WITH_STOCK_SYMBOLS = "Symbol" -COLUMN_WITH_STOCK_PRICE = "Current stock price" - -def get_stock_price(SYMBOL): - # Endpoint to fetch current stock price - url = f"https://api.twelvedata.com/price?symbol={SYMBOL}&apikey={TWELVE_DATA_API_KEY}" - - # Make the GET request to fetch the data - response = requests.get(url) - - if response.status_code == 200: - output = response.json() - return output['price'] - else: - return False - -# Get symbols from SeaTable base and update the current stock prices -def update_stock_price(): - for row in base.list_rows(TABLE_WITH_STOCK_SYMBOLS): - # 2. Fetches the current stock price from Twelve Data API - current_price = get_stock_price(row['Symbol']) - # 3. Update the stock price in the table - base.update_row(TABLE_WITH_STOCK_SYMBOLS, row.get('_id'), {COLUMN_WITH_STOCK_PRICE: current_price}) - # 4. Display the fetched value in the console - print(f"The current price of {row['Symbol']} is: {current_price}") - -if __name__ == '__main__': - base = Base(API_TOKEN, SERVER_URL) - base.auth() - update_stock_price() - print("Update complete.") -``` diff --git a/docs/scripts/python/introduction.md b/docs/scripts/python/introduction.md deleted file mode 100644 index b89d92ff..00000000 --- a/docs/scripts/python/introduction.md +++ /dev/null @@ -1,121 +0,0 @@ -# Introduction - -Python scripts connect to SeaTable databases with the python library [seatable-api](https://pypi.org/project/seatable-api/). You can find the source code on [GitHub](https://github.com/seatable/seatable-api-python). Python scripts can be created and executed directly in a base using a SeaTable component called Python Pipeline. You can also choose to run scripts locally. Where you run your Python script has consequences on the available libraries and authentication. - -!!! warning "Indents are important" - - Please take care of indentations! Indentation is mandatory in Python to define the blocks of statements. The number of spaces must be uniform in a block of code. It is preferred to use whitespaces instead of tabs to indent in Python. If the indentations are wrong, the scripts will throw errors or not work as expected! - -## Libraries - -The current Python Pipeline ships with Python 3.12 and a bundle of [third party libraries](/scripts/python/common_questions/#list-of-libraries-supported-in-the-cloud-environment). One of the bundled libraries and the main library to interact with SeaTable bases is [seatable-api](https://github.com/seatable/seatable-api-python). - -At a minimum, the Base and context function from the seatable-api library must be imported. Additionally, you can import functions from the bundled libraries. - -```python -from seatable_api import Base, context -from datetime import datetime -``` - -When running Python scripts locally, you can take advantages of the uncountable number of Python libraries. - -## Authentication - -Python (in comparison to JavaScript) scripts need an authentication. SeaTable provides multiple tokens to obtain authorization to read and write a base. But let's keep things simple! If you develop Python scripts in SeaTable, just use the context object `context.api_token` or provide a so called `API token` of a base (see [Authorization with API token below](#authorization-with-api-token)). If you want to learn more about authentication, all details can be found in the [SeaTable API Reference](https://api.seatable.com/reference/authentication). - -!!! warning "Protect your credentials" - - Please be aware that a python script is readable for all users, who have access to this base. Therefore try to avoid exposing your credentials directly in the code! Use environment variables or `.env` files instead. - -### Authorization with API token - -Using this method, you will use the API token of the base. Within SeaTable's integrated Python editor, authentication can be done very simply thanks to the [context object](https://developer.seatable.com/scripts/python/objects/context/). In local environment, the context object is not available. You'll have to provide directly the `api_token` and the `server_url` variables. The API token can be directly [generated in the web interface](https://seatable.com/help/erzeugen-eines-api-tokens/). - -=== "SeaTable's integrated Python editor" - - ```python - from seatable_api import Base, context # (1)! - base = Base(context.api_token, context.server_url) - base.auth() - ``` - - 1. Don't forget to import `context`. Thanks to this, you won't have to manually provide any credential. - -=== "Local execution" - - ```python - from seatable_api import Base # (1)! - - API_TOKEN = 'c3c75dca2c369848455a39f4436147639cf02b2d' # (2)! - SERVER_URL = 'https://cloud.seatable.io' - - base = Base(API_TOKEN, SERVER_URL) - base.auth() - ``` - - 1. No need to import `context` here as it won't actually be available. - - 2. This is for demonstration purpose only: try to avoid exposing your credentials directly in the code! Use environment variables or `.env` files instead. - -It is even possible to develop a Python script in the way that it could be [executed both in the cloud and locally](/scripts/python/common_questions/#how-to-make-the-script-support-both-local-and-cloud-run) without changing the code. - -### Authorization with account object - -Instead of using an API token, you can also authenticate using the `account` object. Doing so, you'll have to provide both your `username` and `password` (in addition to the `server_url` variable). - -Whereas the API token is specific to a base, the `account` object is general and gives you access to all your bases (as when you log on SeaTable). To get a specific base, you'll have to use the `get_base` function, given the workspace ID `workspace_id` and the name of the base `base_name`. To get the workspace ID: - -1. Go to the SeaTable home page. - -2. Click the base whose workspace ID you want to determine. - -3. When the selected base has opened, you can read the Workspace ID at the top of the page URL, which actually looks like *https://cloud.seatable.io/workspace/`84254`/dtable/MyBase* (or any `server_url` instead of *https://cloud.seatable.io*). - - -```python -from seatable_api import Account -account = Account(username, password, server_url) -account.auth() -base = account.get_base(workspace_id, base_name) -``` - -### Authorization expiration handling - -!!! info "This feature works with SeaTable version 3.1+" - -In some cases, the program needs to run for a (very) long time, the code of base operations usually being located in a `while` or `for` loop. In this case, authorization may expire during execution and cause the program to break. We provide an exception called `AuthExpiredError` that can be caught for reauthorization. - -```python -from seatable_api import Base, context -from seatable_api.exception import AuthExpiredError - -server_url = context.server_url or 'https://cloud.seatable.io' -api_token = context.api_token or 'c3c75dca2c369849455a39f4436147639cf02b2d' - -base = Base(api_token, server_url) -base.auth() - -while True: # (1)! - try: - base.append_row('Table1', {"xxx":"xxx"}) - ... - except AuthExpiredError: - base.auth() -``` - -1. Always be careful with infinite loops! - -## Base operations limits - -As Python scripts are tailored for huge base manipulations and because they actually rely on the [SeaTable API](https://api.seatable.com), you might encounter [Rate](https://api.seatable.com/reference/limits#general-rate-limits) or [Size](https://api.seatable.com/reference/limits#size-limits) limits if you are not vigilant. Here are a few tips to avoid reaching the limits: - -- Be always careful with operations in `for` or `while` loops (ensure the ending conditions will be reached) - -- Use *batch* operations as often as possible. Replace for example several `base.append_row` calls with a single `base.batch_append_rows` call. Here are the main batch functions: - - - `base.batch_append_rows` - - `base.batch_update_rows` - - `base.batch_delete_rows` - - `base.batch_update_links` - -- Learn more about [lowering your calls](https://seatable.com/api-optimization/) diff --git a/docs/scripts/python/objects/big_data.md b/docs/scripts/python/objects/big_data.md deleted file mode 100644 index 8fe9d54a..00000000 --- a/docs/scripts/python/objects/big_data.md +++ /dev/null @@ -1,23 +0,0 @@ -# Big data storage - -## Insert rows into big data storage - -!!! abstract "big_data_insert_rows" - - Batch insert rows into big data storage. - - ``` python - base.big_data_insert_rows(table_name, rows_data) - ``` - - __Output__ Dict containing a single `inserted_row_count` key with the number of rows actually inserted in the big data storage. - - __Example__ - - ``` python - rows = [ - {'Name': "A"}, - {'Name': "B"} - ] - base.big_data_insert_rows('Table1', rows_data=rows) - ``` diff --git a/docs/scripts/python/objects/index.md b/docs/scripts/python/objects/index.md deleted file mode 100644 index 52f34a72..00000000 --- a/docs/scripts/python/objects/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# Predefined objects and methods (Python) - -This manual list all available objects and methods (also called functions) that are available within Python scripts in SeaTable. When running directly in SeaTable, Python scripts have the ability to access the [base context](context.md). [Date utilities](date-utils.md) are also available. - -If you compare JavaScript and Python, you will notice that Python has no specific output methods. This is not necessary, because the output is either written into the base or directly returned by the methods. Besides, you'll see that **Python methods never accept objects** for table, view or row selection arguments, but only their names/`_ids` as strings. Unless otherwise stated, **all method arguments are required**. - -## Data model - -{% - include-markdown "includes.md" - start="" - end="" -%} - -!!! info "Need a specific function?" - - The Python library `seatable_api` does not yet cover all available functions of the SeaTable API. If you are missing a special function, please contact us at [support@seatable.io](mailto:support@seatable.io) and we will try to add the missing functions. - -## Getting started - -Let's make this concrete and let us look at some basic examples. - -1. Jump to your SeaTable web interface -2. Create a new Python script -3. Copy the following code -4. Run the script - -You will learn from these examples, that it is quite easy to read, output and even manipulate the data of a base inside SeaTable with the predefined objects and the corresponding methods. - - - -=== "1. Add a table to a base" - - This examples shows how to add a table to an existing base. - - ``` python - from seatable_api import Base, context - base = Base(context.api_token, context.server_url) - base.auth() # (1)! - - columns=[ - { - "column_type" : "text", - "column_name": "name" - }, - { - "column_type": "number", - "column_name": "age" - } - ] - - base.add_table("ScriptTest", lang='en', columns=columns) - ``` - - 1. These three lines are always required to authorize against the base in SeaTable. - -=== "2. Add a row to this new table" - This examples shows how to add a record to a table. The example script assumes that a table "ScriptTest" table with two columns "name" and "age" exists in the base. - - ``` python - from seatable_api import Base, context - base = Base(context.api_token, context.server_url) - base.auth() - - row_data = { - 'name': 'Tom', - 'age': 18 - } - - base.append_row('ScriptTest', row_data) - ``` diff --git a/docs/scripts/python/objects/users.md b/docs/scripts/python/objects/users.md deleted file mode 100644 index 5753b226..00000000 --- a/docs/scripts/python/objects/users.md +++ /dev/null @@ -1,25 +0,0 @@ -# Users - -## Get user info - -!!! abstract "get_user_info" - - Returns the name of the user and his ID (the one you can see in your [profile](https://seatable.com/help/persoenliche-einstellungen/)). The username you have to provide is a unique identifier ending by `@auth.local`. This is **neither** the email address of the user **nor** its name. - - ``` python - base.get_user_info(username) - ``` - - __Output__ Dict containing `id_in_org` and `name` keys - - - __Example__ - - ``` python - from seatable_api import Base, context - - base = Base(context.api_token, context.server_url) - base.auth() - user_info = base.get_user_info("aea9e807bcfd4f3481d60294df74f6ee@auth.local") - print(user_info) - ``` diff --git a/docs/scripts/sql/introduction.md b/docs/scripts/sql/introduction.md deleted file mode 100644 index 691bf370..00000000 --- a/docs/scripts/sql/introduction.md +++ /dev/null @@ -1,298 +0,0 @@ -# SQL in SeaTable - -SQL queries are the most powerful way to access data stored in a base. If you're not familiar with SQL syntax, we recommend using first the [SQL query plugin](https://seatable.com/help/anleitung-zum-sql-abfrage-plugin/). If some tables in a base are archived, archived rows are also queried, as well as rows that are not archived yet. - -!!! info "Backticks for table or column names containing special characters or using reserved words" - For SQL queries, you can use numbers, special characters or spaces in the names of your tables and columns. However, you'll **have to** escape these names with backticks in order for your query to be correctly interpreted, for example `` SELECT * FROM `My Table` ``. - - Similarly, if some of your table or column names are the same as [SQL function](./functions.md) names (for example a date-type column named `date`), you'll also **have to** escape them in order for the query interpreter to understand that it's not a function call missing parameters, but rather a table or column name. - -## Supported SQL Syntax - -Currently only `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements are supported (the last three require version 2.7 or later). You'll find below the syntax for these statements. - -Please note that the SQL syntax is case insensitive: we use only upper-cased instructions here for ease of reading (differentiating SQL instructions from table or column names). - -### Retrieving row(s) - -!!! abstract "SELECT" - The `SELECT` statement allows you to retrieve an eventually filtered, sorted and/or grouped list of the rows from a specific table. Each returned row is a JSON object. The keys of the object are the column **keys, NOT the column names**. To use column names as keys, the `convert_keys` parameter (available since version 2.4) in query request should be `true` (which is the default value when using `base.query` for both JavaScript and Python scripts). - The syntax of `SELECT` statement is: - - ``` - SELECT [Column List] FROM tableName [Where Clause] [Group By Clause] [Having Clause] [Order By Clause] [Limit Option] - ``` - - `[Column List]` is the list of columns you want to retrieve, separated by commas. If you want to retrieve all the columns, you can use a wildcard (`*`). - You can consult specific sections for [Where, Group By, Having or Order By clauses](#where-group-by-having-and-order-by-clauses) - `Limit Option` uses MySQL format. The general syntax is `LIMIT ... OFFSET ...`. This parameters are optional. Unless you specify a higher limit, the method returns **a maximum of 100 rows**. The maximum number of rows returned is **10000** no matter the limit specified in the SQL statement. The `OFFSET` will help you retrieve the following rows in other queries - - __Example__ `SELECT * FROM Table1 LIMIT 10000` returns the first 10000 rows, `SELECT * FROM Table1 LIMIT 10000 OFFSET 10000` returns the next 10000 rows - - - Since version 4.3, basic **implicit** *join* query is supported, for example: - - ``` - SELECT ... FROM Table1, Table2 WHERE Table1.column1 = Table2.column2 AND ... - ``` - - The *join* queries have the following restrictions: - - - You **must not** explicitly write JOIN keyword - - Only *inner join* is supported; *left join*, *right join*, and *full join* are not supported. - - Tables in the `FROM` clause should be unique (no duplicate tables). - - Each table in the `FROM` clause should be associated with at least one join condition. - - Join conditions should be placed in the `WHERE` clause, and eventually connected with one or more `AND` operators. - - Join conditions can only use **equality operator** on columns, e.g. `Table1.column1 = Table2.column2`. - - Columns in join conditions must be indexed, unless the table is not archived. - -!!! info "Field aliases" - Field alias with `AS` syntax is supported. For example, `SELECT table.a as a FROM table` returns rows whose first column is keyed by "a". There are two important points to note however: - - - Field alias can be referred in `GROUP BY`, `HAVING` and `ORDER BY` clauses. For example, `SELECT i.amount AS a, COUNT(*) FROM Invoices AS i GROUP BY a HAVING a > 100` is valid. - - Field alias cannot be referred in `where` clause. E.g., `select t.registration as r, count(*) from t group by r where r > "2020-01-01"` will report syntax error. - -!!! info "Aggregation functions" - While retrieving rows, you can add aggregation functions to the list of columns if you specify a [GroupByClause](#where-group-by-having-and-order-by-clauses). The available functions are: - - - `COUNT` returns the number of non-empty values in a specific column or for all columns with `COUNT(*)` - - `SUM` computes the sum of values in a specific column, for example `SUM(Invoices.Amount)` - - `MAX` retrieves the greatest value in a specific column, for example `MAX(Invoices.Amount)` - - `MIN` retrieves the smallest value in a specific column, for example `MIN(Invoices.Amount)` - - `AVG` computes the average of non-empty values in a specific column, for example `AVG(Invoices.Amount)` - - __Example__ - - ``` - SELECT Customer, SUM(Amount) from Invoices GROUP BY Customer - ``` - -### Modifying database content - -!!! abstract "INSERT" - - !!! warning "Enterprise subscription needed" - - `INSERT` requires [Big Data](https://seatable.com/help/big-data-capabilities/) storage support, which is available only with an [Enterprise subscription](https://seatable.com/help/subscription-plans/#seatable-cloud-enterprise-search). - - `INSERT` allows you to append a new row to a table. `INSERT` statement **only** supports bases that have been [archived](https://seatable.com/help/aktivieren-des-big-data-backends-in-einer-base/#designation-as-archivebackend-search). The rows will be inserted into big-data storage. It'll return error if the base is not archived yet. - - - If you want to insert rows in a non-archived base, please use the API dedicated functions (e.g. the [Python API](../python/objects/rows.md#add-rows)). - - ``` - INSERT INTO table_name [column_list] VALUES value_list [, ...] - ``` - - - `column_list` is a list of column names surrounded by parentheses. If omitted, it defaults to all updatable columns. - - `value_list` is a list of values surrounded by parentheses. Values must be in the same order as the column list, for example: `(1, "2", 3.0)`. - - Columns with multiple values, such as "multiple select"-type column , requires values to be surrounded by parentheses, for example: `(1, "2", 3.0, ("foo", "bar"))`. - - Values of "single select" and "multiple select"-type columns must be option names, not option keys. - - Few column types are **not allowed** to insert: - - - built-in columns, such as `_id`, `_ctime`. - - image, file, formula, link, link-formula, geolocation, auto-number, button - - __Example__ - - ``` - INSERT INTO Table1 (Name, Age) values ('Erika', 38) - ``` - -!!! abstract "UPDATE" - `UPDATE` allows you to update one or multiple existing rows of a table. Unlike the `INSERT` statement, `UPDATE` allows you to update rows in both normal and big-data storage. `WhereClause` is optional. However, keep in mind that if omitted, **all rows** will be updated! - - ``` - UPDATE table_name SET column_name = value [, ...] [WhereClause] - ``` - - !!! warning "Only constant values in SET clause" - The `value` in the SET clause must be a **constant** (string, number, or boolean). Functions (e.g. `upper()`, `round()`, `now()`, `if()`), arithmetic expressions (e.g. `Amount + 10`), and column references are **not supported** in the SET clause. Functions and expressions can only be used in `SELECT` and `WHERE` clauses. - - - Columns with multiple values, such as "multiple select"-type column, requires values to be surrounded by parentheses, for example: `("foo", "bar")`. - - Values of "single select" and "multiple select"-type columns must be option names, not option keys. - - Few column types are **not allowed** to update: - - - built-in columns, such as `_id`, `_ctime`. - - image, file, formula, link, link-formula, geolocation, auto-number, button - - __Example__ - - ``` - UPDATE Contacts SET Adult=true WHERE Age>=18 - ``` - - __Example__ - - ``` - UPDATE Contacts SET Adult=true, `Age group`="18+" WHERE Age>=18 - ``` - - If `Age group` is a "single select"-type column, the option you want to select (here "18+") has to exist already. - -!!! abstract "DELETE" - `DELETE` allows you to delete one or multiple existing rows of a table. Unlike the `INSERT` statement, `DELETE` allows you to delete rows in both normal and big-data storage. `WhereClause` is optional. However, keep in mind that if omitted, **all rows** will be deleted! - - ``` - DELETE FROM table_name [WhereClause] - ``` - - __Example__ - - ``` - DELETE FROM Contacts WHERE Age<18 - ``` - - -### WHERE, GROUP BY, HAVING and ORDER BY clauses - -!!! abstract "WHERE clause" - Most SQL syntax can be used in the `WHERE` clause, including arithmetic expressions, comparison operators, `[NOT] LIKE`, `IN`, `BETWEEN ... AND ...`, `AND`, `OR`, `NOT`, `IS [NOT] TRUE`, `IS [NOT] NULL`. - - - Arithmetic expressions only support numbers. - - Time constants should be strings in ISO format (e.g. "2020-09-08 00:11:23"). Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). - -!!! abstract "GROUP BY clause" - `GROUP BY` uses strict syntax. The selected fields must appear in the clause list, except for aggregation functions (`COUNT`, `SUM`, `MAX`, `MIN`, `AVG`) and formulas (see extended syntax section below). - -!!! abstract "HAVING clause" - `HAVING` filters rows resulting from the `GROUP BY` clause. Only fields referred in the `GROUP BY` clause or aggregation functions (such as "SUM") can be used in `HAVING` clause. Other syntax is the same as specified for the `WHERE` clause. - -!!! abstract "ORDER BY clause" - Fields in `ORDER BY` list must be a column or an expression in the selected fields. For example, `select a from table order by b` is invalid; while `select a, b from table order by b` and `select abs(a), b from table order by abs(a)` are valid. - -### LIKE and BETWEEN operators - -!!! abstract "LIKE operator" - `LIKE` only supports strings. The key word `ILIKE` can be used instead of `LIKE` to make the match case insensitive. The percent sign `%` you will use in the `LIKE` expression represents zero, one, or multiple characters. - - __Example__ - ``` - SELECT `Full Name` from Contacts WHERE `Full Name` LIKE "% M%" - ``` - returns every record with a last name starting with M (considering that the `Full Name` fields is actually composed like "`First Name` `Last Name`") - -!!! abstract "BETWEEN operator" - `BETWEEN lowerLimit AND upperLimit` only supports numbers and time. `lowerLimit` and `upperLimit` are included in the search. They have to be in the right order (if `upperLimit`<`lowerLimit`, no records will be found). - - __Example__ - ``` - SELECT * from Contacts WHERE Age BETWEEN 18 AND 25 - ``` - returns every record with an age between 18 and 25 (both included) - - -## Data types - -### SeaTable <> SQL mapping - -Below is the mapping of SeaTable column types to SQL data types. - -| SeaTable column type | SQL data type | Query result format | Use in WHERE clause | Use in GROUP BY / ORDER BY clause | -| :-------------------- | :--------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------- | -| text | String | | Supported | Supported | -| long-text | String | Raw text in Markdown format | Supported | Supported | -| number | Float | | Supported | Supported | -| single-select | String | Returned rows contain the option name. | Supported. Refer an option by its name. E.g. `WHERE single_select = "New York"`. | Order by the definition order of the options | -| multiple-select | List of strings | Returned rows contain the option names. | Supported. Refer an option by its name. E.g. `WHERE multi_select = "New York"`. More details in the "List types" section below. | More details in the "List types" section below. | -| checkbox | Boolean | | Supported | Supported | -| date | Datetime | Time strings in RFC 3339 format | Supported. Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | -| image | List of URL for images | A JSON array with image URLs as elements | Supported. More details in the "List types" section below. | Supported. More details in the "List types" section below. | -| file | Will be returned as JSON format string when queried. | Not supported | Not Supported | Not Supported | -| collaborator | List of user IDs | Format is like 5758ec...6d3388@auth.local. If you need user names, you have to convert with SeaTable APIs. | Supported. More details in the "List types" section below. | Supported. More details in the "List types" section below. | -| link to other records | List of linked rows | Supported. More details in the "List types" section below. | Supported. More details in the "List types" section below. | Supported. More details in the "List types" section below. | -| formula | The type depends on the return value of the formula. | Depends on the type of the return value | Depends on the type of the return value | Depends on the type of the return value | -| \_creator | User ID as string | Format is like 5758ec...6d3388@auth.local. If you need user names, you have to convert with SeaTable APIs. | Supported | Supported | -| \_ctime | Datetime | Time strings in RFC 3339 format | Supported. Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | -| \_last_modifier | User ID as string | Format is like 5758ec...6d3388@auth.local. If you need user names, you have to convert with SeaTable APIs. | Supported | Supported | -| \_mtime | Datetime | Time strings in RFC 3339 format | Supported. Constants are expressed in strings in ISO format. e.g. "2006-1-2" or "2006-1-2 15:04:05". Since 2.8 version, strings in RFC 3339 format are supported (such as "2020-12-31T23:59:60Z"). | Supported | -| auto number | String | | Supported | Supported | -| url | String | | Supported | Supported | -| email | String | | Supported | Supported | -| duration | Float | Returned in seconds | Supported | Supported | - -### List types - -In SeaTable, two categories of column types are list types (columns with multiple values): - -- Built-in list types: including multiple selection, image, file, collaborator and link to other records. -- Formula columns dealing with linked records (using either `{link.column}` or `lookup`) and link formula columns whose formula is `lookup`, `findmin` or `findmax`. - -When referring to a list-type column in a `WHERE` clause, the following rules apply, depending on the type for the list elements. If an operator is not listed below, it's unsupported. - -| Element Type | Operator | Rule | -| :------------ | :---------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | -| string | `IN`, extended list operators (e.g. `HAS ANY OF`) | Follow the rules of the operator. | -| string | `LIKE`, `ILIKE` | Always take the first element for comparison; if there is no element, use an empty string (""). - | -| string | `IS NULL` | Return `true` when the list is empty or when there is no data in the cell. | -| string | =, != | Always take the first element for comparison; if there is no element, use an empty string (""). | -| float | `IN`, extended list operators (e.g. `HAS ANY OF`) | Follow the rules of the operator. | -| float | =, !=, <, <=, >, >=, between | If there is only 1 element, use that element; otherwise only return `true` for `!=` operator. | -| float | `IS NULL` | Return `true` when the list is empty or when there is no data in the cell. | -| float | Arithmetics operations such as +, -, * or / | Use the first element for calculation. | -| Datetime | `IN`, extended list operators (e.g. `HAS ANY OF`) | Follow the rules of the operator. | -| Datetime | =, !=, <, <=, >, >=, between | If there is only 1 element, use that element; otherwise only return `true` for `!=` operator. | -| Datetime | `IS NULL` | Return `true` when the list is empty or when there is no data in the cell. | -| bool | `IS TRUE` | Always take the first element for comparison; return false if there are no elements. -| linked record | | Follow the rules for the type of the display column. | - -When a list column is returned in a selected field, only the ten first elements are returned. - -When used in `GROUP BY` or `ORDER BY` clauses, the elements for each list will first be sorted in ascending order, then the lists will be sorted by the rules below: - -- Compare the elements one by one, list with smaller element is sorted before list with larger element. -- If all elements compared in step 1 are equal, shorter list is sorted before longer list. -- Otherwise the two lists are equal. - -If a list column is passed as parameter to a formula, and the parameter expects a scalar value, the first element will be used. And if the element is a linked record, the value of its display column will be used. - -When applying aggregate functions (min, max, sum, avg) to a list column, if there is only 1 element in the list, use that element; otherwise this row will not be aggregated. - -### NULL values - -NULL value is distinct from 0. It represents a missing value. The following values are treated as NULL: - -- Empty cells in a table. -- Values which cannot be converted to the column type. -- Empty strings (""). This is different from standard SQL. -- Lists are treated as NULL based on the rules described in the "List Types" section. -- Functions or formula columns that return error. - -In the `WHERE` clause: - -- Arithmetics operations such as +, -, * or / on NULL values will return NULL. -- `!=`, `NOT LIKE`, `NOT IN`, `NOT BETWEEN`, `HAS NONE OF`, `IS NOT TRUE`, and `IS NULL` operations will return `true` when the value is NULL. -- `AND`, `OR`, `NOT` treat NULL values as `false`. -- Aggregate functions (min, max, sum, avg) will ignore NULL values. - -In formulas, NULL values will be converted to 0 or an empty string. - -## Extended syntax - -### Using formulas in SQL query - -You may use a formula syntax that's almost the same as SeaTable's formulas in SQL queries. There are a few special notes: - -- Link formulas are not supported. e.g. {link.age} is invalid. -- Reference to columns should not be enclosed by curly brackets ("{}"). Don't write `SELECT abs({column}) FROM table`. Write `SELECT abs(column) FROM table`. This is consistent with standard SQL syntax. -- You have to use backticks ("\`\`") to enclose column references, when column name contains space or "-". E.g. ```SELECT abs(`column-a`) FROM table```. -- You cannot use column alias in formulas. E.g. `SELECT abs(t.column) FROM table AS t;` is invalid. -- Formulas can be used in `GROUP BY` and `ORDER BY` clauses. - -For an exhaustive list of available functions, please refer to the complete [function reference](./functions.md). - -### Extended list operators - -Some column types in SeaTable have list values. The SeaTable UI supports a few special filters for such types, which are `HAS ANY OF`, `HAS ALL OF`, `HAS NONE OF` and `IS EXACTLY`. You can use the same syntax to filter such columns with SQL. For all these operators, the list of string constant are enclosed with brackets, just like the syntax for `IN`. Please note that the order of values in the list is not taken into account. - -__Example__ `SELECT * FROM table WHERE city HAS ANY OF ("New York", "Paris")` will retrieve all rows that contain either "New York" or "Paris" in the "multiple select"-type column `city` - -## Big Data storage indexes - -To improve query performance, SeaTable will automatically create indexes for the rows stored in big data storage engine. Currently, text, number, date, single select, multiple select, collaborators, creator, create date, modifier and modification date columns are indexed. - -When you add or delete a column in a table, the index for this column is not added/removed immediately. Indexes creation and deletion are triggered in two cases: - -1. When you archive the table for the next time, indexes are created for new columns and indexes for removed columns are removed. -2. Users may manage indexes from "index management" UI. You can open it from the "Big data management" menu in the base. diff --git a/docs/sql/clauses.md b/docs/sql/clauses.md new file mode 100644 index 00000000..4df718bc --- /dev/null +++ b/docs/sql/clauses.md @@ -0,0 +1,56 @@ +# Clauses + +!!! info "Backticks for special names" + Escape table or column names that contain spaces, special characters, or match [SQL function](./functions.md) names with backticks: `` SELECT * FROM `My Table` ``. + +## WHERE + +Most SQL syntax can be used in the `WHERE` clause: arithmetic expressions, comparison operators, `[NOT] LIKE`, `IN`, `BETWEEN ... AND ...`, `AND`, `OR`, `NOT`, `IS [NOT] TRUE`, `IS [NOT] NULL`. + +- Arithmetic expressions only support numbers +- Time constants must be strings in ISO format (e.g. `"2020-09-08 00:11:23"`). Since version 2.8, RFC 3339 format is also supported (e.g. `"2020-12-31T23:59:60Z"`) + +## GROUP BY + +`GROUP BY` uses strict syntax. Selected fields must appear in the GROUP BY list, except for aggregation functions (`COUNT`, `SUM`, `MAX`, `MIN`, `AVG`) and formulas. + +## HAVING + +`HAVING` filters rows resulting from `GROUP BY`. Only fields in the GROUP BY list or aggregation functions can be used. Other syntax is the same as WHERE. + +## ORDER BY + +Fields in the `ORDER BY` list must be a column or expression that appears in the selected fields. + +__Valid:__ +``` +SELECT a, b FROM table ORDER BY b +SELECT abs(a), b FROM table ORDER BY abs(a) +``` + +__Invalid:__ +``` +SELECT a FROM table ORDER BY b +``` + +## LIKE + +`LIKE` only supports strings. Use `ILIKE` for case-insensitive matching. The percent sign `%` represents zero, one, or multiple characters. + +__Example__ + +``` +SELECT `Full Name` FROM Contacts WHERE `Full Name` LIKE "% M%" +``` + +Returns every record with a last name starting with M. + +## BETWEEN + +`BETWEEN lowerLimit AND upperLimit` supports numbers and time. Both limits are included. They must be in the correct order. + +__Example__ + +``` +SELECT * FROM Contacts WHERE Age BETWEEN 18 AND 25 +``` diff --git a/docs/sql/data-types.md b/docs/sql/data-types.md new file mode 100644 index 00000000..3132f90e --- /dev/null +++ b/docs/sql/data-types.md @@ -0,0 +1,78 @@ +# Data Types + +## SeaTable to SQL mapping + +| SeaTable column type | SQL data type | Query result format | WHERE | GROUP BY / ORDER BY | +|:---|:---|:---|:---|:---| +| text | String | | Supported | Supported | +| long-text | String | Raw Markdown | Supported | Supported | +| number | Float | | Supported | Supported | +| single-select | String | Option name | By option name | By definition order | +| multiple-select | List of strings | Option names | By option name | See list types | +| checkbox | Boolean | | Supported | Supported | +| date | Datetime | RFC 3339 format | ISO or RFC 3339 strings | Supported | +| image | List of URLs | JSON array | See list types | See list types | +| file | JSON string | | Not supported | Not supported | +| collaborator | List of user IDs | `xxx@auth.local` | See list types | See list types | +| link to other records | List of linked rows | | See list types | See list types | +| formula | Depends on return value | | Depends on type | Depends on type | +| \_creator | String (user ID) | `xxx@auth.local` | Supported | Supported | +| \_ctime | Datetime | RFC 3339 format | ISO or RFC 3339 strings | Supported | +| \_last\_modifier | String (user ID) | `xxx@auth.local` | Supported | Supported | +| \_mtime | Datetime | RFC 3339 format | ISO or RFC 3339 strings | Supported | +| auto number | String | | Supported | Supported | +| url | String | | Supported | Supported | +| email | String | | Supported | Supported | +| duration | Float | In seconds | Supported | Supported | + +## List types + +Two categories of columns have list values: + +- **Built-in**: multiple select, image, file, collaborator, link to other records +- **Formula-based**: link formulas using `lookup`, `findmin`, or `findmax` + +### WHERE clause rules for list types + +| Element type | Operator | Rule | +|:---|:---|:---| +| string | `IN`, `HAS ANY OF`, etc. | Follow operator rules | +| string | `LIKE`, `ILIKE` | Uses first element; empty string if no element | +| string | `IS NULL` | True when list is empty | +| string | `=`, `!=` | Uses first element | +| float | `IN`, `HAS ANY OF`, etc. | Follow operator rules | +| float | `=`, `!=`, `<`, `<=`, `>`, `>=`, `BETWEEN` | Uses single element; only `!=` returns true for multiple | +| float | `IS NULL` | True when list is empty | +| float | `+`, `-`, `*`, `/` | Uses first element | +| datetime | Same rules as float | | +| bool | `IS TRUE` | Uses first element; false if empty | +| linked record | | Follows rules for the display column type | + +Only the first ten elements are returned in query results. + +### Sorting list types + +In GROUP BY / ORDER BY, elements are first sorted ascending within each list, then lists are compared element by element. Shorter lists sort before longer lists when all compared elements are equal. + +### Aggregation on list types + +For `MIN`, `MAX`, `SUM`, `AVG`: if the list has exactly one element, that element is used. Otherwise the row is not aggregated. + +## NULL values + +NULL represents a missing value (distinct from 0). These are treated as NULL: + +- Empty cells +- Values that cannot be converted to the column type +- Empty strings (`""`) +- Empty lists (see list types rules) +- Formulas that return an error + +### NULL in WHERE + +- Arithmetic on NULL returns NULL +- `!=`, `NOT LIKE`, `NOT IN`, `NOT BETWEEN`, `HAS NONE OF`, `IS NOT TRUE`, `IS NULL` return `true` for NULL +- `AND`, `OR`, `NOT` treat NULL as `false` +- Aggregate functions ignore NULL values + +In formulas, NULL is converted to 0 or empty string. diff --git a/docs/sql/extended-syntax.md b/docs/sql/extended-syntax.md new file mode 100644 index 00000000..78b7a058 --- /dev/null +++ b/docs/sql/extended-syntax.md @@ -0,0 +1,41 @@ +# Extended Syntax + +## Formulas in SQL queries + +You can use SeaTable formula syntax directly in SQL queries. A few differences from SeaTable's built-in formulas: + +- Link formulas (e.g. `{link.age}`) are **not** supported +- Column references are **not** enclosed in curly brackets: use `abs(column)`, not `abs({column})` +- Use backticks for column names with spaces or hyphens: `` abs(`column-a`) `` +- Column aliases cannot be used in formulas: `abs(t.column)` is invalid +- Formulas can be used in `GROUP BY` and `ORDER BY` clauses + +For the complete list of available functions, see the [function reference](./functions.md). + +## Extended list operators + +SeaTable supports special operators for list-type columns (multiple select, collaborator, etc.): + +| Operator | Description | +|---|---| +| `HAS ANY OF` | Row contains at least one of the values | +| `HAS ALL OF` | Row contains all of the values | +| `HAS NONE OF` | Row contains none of the values | +| `IS EXACTLY` | Row contains exactly these values (order-independent) | + +Values are enclosed in parentheses, like the `IN` operator. + +__Example__ + +``` +SELECT * FROM table WHERE city HAS ANY OF ("New York", "Paris") +``` + +## Big Data storage indexes + +SeaTable automatically creates indexes for rows in big data storage to improve query performance. Indexed column types: text, number, date, single select, multiple select, collaborators, creator, create date, modifier, modification date. + +Indexes are updated when: + +1. The table is archived the next time +2. A user triggers index management from the "Big data management" menu in the base diff --git a/docs/scripts/sql/functions.md b/docs/sql/functions.md similarity index 100% rename from docs/scripts/sql/functions.md rename to docs/sql/functions.md diff --git a/docs/sql/index.md b/docs/sql/index.md new file mode 100644 index 00000000..be60774a --- /dev/null +++ b/docs/sql/index.md @@ -0,0 +1,21 @@ +# SQL + +SQL queries are the most powerful way to access data stored in a base. SeaTable supports `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements. SQL is not a standalone interface but is used through the [Python](../python/) or [JavaScript](../javascript/) API via `base.query()`: + +=== "Python" + + ```python + results = base.query("SELECT * FROM Table1 LIMIT 100") + ``` + +=== "JavaScript" + + ```js + const results = await base.query("SELECT * FROM Table1 LIMIT 100"); + ``` + +SQL syntax is case insensitive. We use upper-cased keywords for readability. + +!!! tip "New to SQL?" + + Try the [SQL query plugin](https://seatable.com/help/anleitung-zum-sql-abfrage-plugin/) in SeaTable to experiment with queries interactively. diff --git a/docs/sql/insert-update-delete.md b/docs/sql/insert-update-delete.md new file mode 100644 index 00000000..3a81b884 --- /dev/null +++ b/docs/sql/insert-update-delete.md @@ -0,0 +1,84 @@ +# INSERT, UPDATE, DELETE + +These statements modify data in a base. Available since SeaTable version 2.7. + +## INSERT + +Appends a new row to a table. `INSERT` **only** works with bases that have [Big Data storage](https://seatable.com/help/big-data-capabilities/) enabled. Rows are inserted into big data storage. + +!!! warning "Enterprise subscription needed" + + `INSERT` requires Big Data storage support, which is available only with an [Enterprise subscription](https://seatable.com/help/subscription-plans/#seatable-cloud-enterprise-search). + +For non-archived bases, use the API functions instead (e.g. [Python `append_row`](/python/objects/rows/#add-rows) or [JavaScript `appendRow`](/javascript/rows/)). + +### Syntax + +``` +INSERT INTO table_name [column_list] VALUES value_list [, ...] +``` + +- `column_list`: column names in parentheses. If omitted, defaults to all updatable columns. +- `value_list`: values in parentheses, matching the column order: `(1, "text", 3.0)` +- Multi-value columns (e.g. multiple select): use nested parentheses: `(1, "text", ("foo", "bar"))` +- Single/multiple select values must be option **names**, not keys + +### Column restrictions + +These column types cannot be inserted: `_id`, `_ctime` (built-in), image, file, formula, link, link-formula, geolocation, auto-number, button. + +__Example__ + +``` +INSERT INTO Table1 (Name, Age) VALUES ('Erika', 38) +``` + +## UPDATE + +Updates one or multiple rows. Works with both normal and big data storage. + +### Syntax + +``` +UPDATE table_name SET column_name = value [, ...] [WHERE ...] +``` + +!!! warning "No WHERE = update all rows" + + If you omit the WHERE clause, **all rows** will be updated. + +!!! warning "Only constant values in SET" + + The `value` in SET must be a constant (string, number, or boolean). Functions, arithmetic expressions, and column references are **not supported** in SET. They can only be used in SELECT and WHERE. + +The same column restrictions and multi-value rules as INSERT apply. + +__Example__ + +``` +UPDATE Contacts SET Adult=true WHERE Age>=18 +``` + +``` +UPDATE Contacts SET Adult=true, `Age group`="18+" WHERE Age>=18 +``` + +## DELETE + +Deletes one or multiple rows. Works with both normal and big data storage. + +### Syntax + +``` +DELETE FROM table_name [WHERE ...] +``` + +!!! warning "No WHERE = delete all rows" + + If you omit the WHERE clause, **all rows** will be deleted. + +__Example__ + +``` +DELETE FROM Contacts WHERE Age<18 +``` diff --git a/docs/sql/select.md b/docs/sql/select.md new file mode 100644 index 00000000..a57bad6e --- /dev/null +++ b/docs/sql/select.md @@ -0,0 +1,82 @@ +# SELECT + +The `SELECT` statement retrieves an optionally filtered, sorted, and grouped list of rows from a table. Each returned row is a JSON object. + +## Syntax + +``` +SELECT [Column List] FROM tableName [WHERE ...] [GROUP BY ...] [HAVING ...] [ORDER BY ...] [LIMIT ... OFFSET ...] +``` + +`[Column List]` is a comma-separated list of columns. Use `*` to retrieve all columns. + +See [Clauses](clauses.md) for details on WHERE, GROUP BY, HAVING, and ORDER BY. + +## Limits + +Unless you specify a higher limit, the method returns a maximum of **100 rows**. The absolute maximum is **10,000 rows**. + +__Example__ + +``` +SELECT * FROM Table1 LIMIT 10000 +``` + +Returns the first 10,000 rows. + +``` +SELECT * FROM Table1 LIMIT 10000 OFFSET 10000 +``` + +Returns the next 10,000 rows. + +## Column keys vs. column names + +By default, returned rows use column **names** as keys (when using `base.query` in Python or JavaScript). The raw API returns column **keys**. This can be controlled with the `convert_keys` parameter. + +## JOIN + +Since version 4.3, basic implicit join queries are supported: + +``` +SELECT ... FROM Table1, Table2 WHERE Table1.column1 = Table2.column2 AND ... +``` + +Restrictions: + +- Do **not** use the `JOIN` keyword explicitly +- Only inner join is supported (no left, right, or full join) +- Tables in the `FROM` clause must be unique +- Each table must have at least one join condition +- Join conditions use equality only: `Table1.column1 = Table2.column2` +- Join conditions must be placed in the `WHERE` clause, connected with `AND` +- Columns in join conditions must be indexed (unless the table is not archived) + +## Field aliases + +Field aliases with `AS` are supported: + +``` +SELECT table.amount AS a, COUNT(*) FROM Invoices AS i GROUP BY a HAVING a > 100 +``` + +- Aliases **can** be used in `GROUP BY`, `HAVING`, and `ORDER BY` +- Aliases **cannot** be used in `WHERE` + +## Aggregation functions + +When using `GROUP BY`, these aggregation functions are available: + +| Function | Description | Example | +|---|---|---| +| `COUNT(*)` | Number of rows | `COUNT(*)` | +| `SUM(col)` | Sum of values | `SUM(Amount)` | +| `MAX(col)` | Maximum value | `MAX(Amount)` | +| `MIN(col)` | Minimum value | `MIN(Amount)` | +| `AVG(col)` | Average of non-empty values | `AVG(Amount)` | + +__Example__ + +``` +SELECT Customer, SUM(Amount) FROM Invoices GROUP BY Customer +``` diff --git a/docs/tests.md b/docs/tests.md deleted file mode 100644 index eec349ba..00000000 --- a/docs/tests.md +++ /dev/null @@ -1,54 +0,0 @@ -## just to test ... - -``` mermaid -graph LR - A[Start] --> B{Error?}; - B -->|Yes| C[Hmm...]; - C --> D[Debug]; - D --> B; - B ---->|No| E[Yay!]; -``` - -``` mermaid -gitGraph - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit - commit -``` - -``` mermaid -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; - E-->C; - F-->E; - G-->F; -``` - -``` mermaid -pie title Pets adopted by volunteers - "Dogs" : 386 - "Cats" : 85 - "Rats" : 15 -``` - -``` mermaid -gantt - title A Gantt Diagram - dateFormat YYYY-MM-DD - section Section - A task :a1, 2014-01-01, 4d - Another task :after a1, 10d - section Another - Task in Another :2014-01-12, 2d - another task :4d -``` diff --git a/mkdocs.yml b/mkdocs.yml index be25f27f..f6befdcc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -56,10 +56,72 @@ plugins: - git-revision-date-localized - include-markdown: rewrite_relative_urls: false - #- redirects: - # redirect_maps: - # 'changelog/server-changelog.md': 'https://seatable.com/changelog/' - # 'changelog/changelog-for-seatable-professional-server.md': 'https://seatable.com/changelog/' + - redirects: + redirect_maps: + # Old "Scripting" section → new language sections + 'scripts/index.md': 'index.md' + 'scripts/python/introduction.md': 'python/index.md' + 'scripts/python/objects/index.md': 'python/objects/metadata.md' + 'scripts/python/objects/metadata.md': 'python/objects/metadata.md' + 'scripts/python/objects/context.md': 'python/objects/context.md' + 'scripts/python/objects/tables.md': 'python/objects/tables.md' + 'scripts/python/objects/views.md': 'python/objects/views.md' + 'scripts/python/objects/columns.md': 'python/objects/columns.md' + 'scripts/python/objects/rows.md': 'python/objects/rows.md' + 'scripts/python/objects/links.md': 'python/objects/links.md' + 'scripts/python/objects/files.md': 'python/objects/files.md' + 'scripts/python/objects/big_data.md': 'python/objects/rows.md' + 'scripts/python/objects/accounts.md': 'python/objects/accounts.md' + 'scripts/python/objects/users.md': 'python/objects/users.md' + 'scripts/python/objects/date-utils.md': 'python/objects/date-utils.md' + 'scripts/python/objects/communication-utils.md': 'python/objects/communication-utils.md' + 'scripts/python/common_questions.md': 'python/index.md' + 'scripts/python/examples/index.md': 'python/index.md' + 'scripts/python/examples/auto-add-rows.md': 'python/index.md' + 'scripts/python/examples/calculate-accumulated-value.md': 'python/index.md' + 'scripts/python/examples/compute-attendance-statistics.md': 'python/index.md' + 'scripts/python/examples/send_email.md': 'python/index.md' + 'scripts/python/examples/generate_barcode.md': 'python/index.md' + 'scripts/python/examples/generate_qrcode.md': 'python/index.md' + 'scripts/python/examples/sync_mysql.md': 'python/index.md' + 'scripts/python/examples/update_stock_price.md': 'python/index.md' + 'scripts/python/examples/merge_pdf.md': 'python/index.md' + 'scripts/python/examples/heic_to_png.md': 'python/index.md' + 'scripts/javascript/objects/index.md': 'javascript/index.md' + 'scripts/javascript/objects/tables.md': 'javascript/tables.md' + 'scripts/javascript/objects/views.md': 'javascript/views.md' + 'scripts/javascript/objects/columns.md': 'javascript/columns.md' + 'scripts/javascript/objects/rows.md': 'javascript/rows.md' + 'scripts/javascript/objects/links.md': 'javascript/links.md' + 'scripts/javascript/objects/context.md': 'javascript/scripting-features.md' + 'scripts/javascript/objects/utilities.md': 'javascript/scripting-features.md' + 'scripts/javascript/objects/output.md': 'javascript/scripting-features.md' + 'scripts/javascript/common_questions.md': 'javascript/index.md' + 'scripts/javascript/examples/index.md': 'javascript/index.md' + 'scripts/javascript/examples/auto-add-rows.md': 'javascript/index.md' + 'scripts/javascript/examples/calculate-accumulated-value.md': 'javascript/index.md' + 'scripts/javascript/examples/compute-attendance-statistics.md': 'javascript/index.md' + 'scripts/sql/introduction.md': 'sql/index.md' + 'scripts/sql/functions.md': 'sql/functions.md' + + # Old "Client APIs" section → new language sections + 'clients/index.md': 'index.md' + 'clients/python_api.md': 'python/index.md' + 'clients/javascript/javascript_api.md': 'javascript/index.md' + 'clients/javascript/metadata.md': 'javascript/metadata.md' + 'clients/javascript/tables.md': 'javascript/tables.md' + 'clients/javascript/views.md': 'javascript/views.md' + 'clients/javascript/columns.md': 'javascript/columns.md' + 'clients/javascript/rows.md': 'javascript/rows.md' + 'clients/javascript/links.md': 'javascript/links.md' + 'clients/javascript/sql_query.md': 'javascript/sql.md' + 'clients/javascript/constants.md': 'javascript/constants.md' + 'clients/javascript/examples/file-upload.md': 'javascript/files.md' + 'clients/php_api.md': 'php/index.md' + 'clients/ruby_api.md': 'ruby/index.md' + + # Old introduction pages + 'introduction/coding_for_beginners.md': 'index.md' # Customization extra: @@ -120,82 +182,47 @@ markdown_extensions: nav: - Introduction: - index.md - - Coding for beginners: introduction/coding_for_beginners.md - Get support: introduction/get_support.md - - Scripting: - - scripts/index.md - - JavaScript: - - Objects & Methods: - - scripts/javascript/objects/index.md - - Tables: scripts/javascript/objects/tables.md - - Views: scripts/javascript/objects/views.md - - Columns: scripts/javascript/objects/columns.md - - Rows: scripts/javascript/objects/rows.md - - Links: scripts/javascript/objects/links.md - - Context: scripts/javascript/objects/context.md - - Utilities: scripts/javascript/objects/utilities.md - - Output: scripts/javascript/objects/output.md - - Examples: - - scripts/javascript/examples/index.md - - 1. Add rows: scripts/javascript/examples/auto-add-rows.md - - 2. Calculate accumulated value: scripts/javascript/examples/calculate-accumulated-value.md - - 3. Compute attendance statistics: scripts/javascript/examples/compute-attendance-statistics.md - - Common questions: scripts/javascript/common_questions.md - - Python: - - Introduction: scripts/python/introduction.md - - Objects & Methods: - - scripts/python/objects/index.md - - Metadata: scripts/python/objects/metadata.md - - Context: scripts/python/objects/context.md - - Tables: scripts/python/objects/tables.md - - Views: scripts/python/objects/views.md - - Columns: scripts/python/objects/columns.md - - Rows: scripts/python/objects/rows.md - - Links: scripts/python/objects/links.md - - Files: scripts/python/objects/files.md - - Big data: scripts/python/objects/big_data.md - - Accounts: scripts/python/objects/accounts.md - - Users: scripts/python/objects/users.md - - Date utilities: scripts/python/objects/date-utils.md - - Communication utilities: scripts/python/objects/communication-utils.md - - Examples: - - scripts/python/examples/index.md - - 1. Add rows: scripts/python/examples/auto-add-rows.md - - 2. Calculate accumulated value: scripts/python/examples/calculate-accumulated-value.md - - 3. Compute attendance statistics: scripts/python/examples/compute-attendance-statistics.md - - 4. Send emails: scripts/python/examples/send_email.md - - 5. Generate barcode: scripts/python/examples/generate_barcode.md - - 6. Generate QR code: scripts/python/examples/generate_qrcode.md - - 7. Sync MySQL: scripts/python/examples/sync_mysql.md - - 8. Watch stock price: scripts/python/examples/update_stock_price.md - - 9. Merge PDF: scripts/python/examples/merge_pdf.md - - 10. Convert HEIC to PNG: scripts/python/examples/heic_to_png.md - - Common questions: scripts/python/common_questions.md - - Query with SQL: - - Introduction: scripts/sql/introduction.md - - SQL function reference: scripts/sql/functions.md + - Python: + - python/index.md + - Metadata: python/objects/metadata.md + - Context: python/objects/context.md + - Tables: python/objects/tables.md + - Views: python/objects/views.md + - Columns: python/objects/columns.md + - Rows: python/objects/rows.md + - Links: python/objects/links.md + - Files: python/objects/files.md + - Accounts: python/objects/accounts.md + - Users: python/objects/users.md + - Date utilities: python/objects/date-utils.md + - Communication utilities: python/objects/communication-utils.md + - JavaScript: + - javascript/index.md + - Tables: javascript/tables.md + - Views: javascript/views.md + - Columns: javascript/columns.md + - Rows: javascript/rows.md + - Links: javascript/links.md + - SQL Queries: javascript/sql.md + - Metadata: javascript/metadata.md + - Files: javascript/files.md + - Constants: javascript/constants.md + - Scripting Features: javascript/scripting-features.md + - PHP: + - php/index.md + - Examples: php/examples.md + - API Reference: php/api-reference.md + - Ruby: ruby/index.md + - SQL: + - sql/index.md + - SELECT: sql/select.md + - INSERT, UPDATE, DELETE: sql/insert-update-delete.md + - Clauses: sql/clauses.md + - Data Types: sql/data-types.md + - Extended Syntax: sql/extended-syntax.md + - Functions: sql/functions.md - Plugin Development: - plugins/index.md - Environments: plugins/environments.md - Available methods: plugins/methods.md - - Client APIs: - - clients/index.md - - JavaScript: - - Introduction: clients/javascript/javascript_api.md - - Objects & Methods: - - Metadata: clients/javascript/metadata.md - - Tables: clients/javascript/tables.md - - Views: clients/javascript/views.md - - Columns: clients/javascript/columns.md - - Rows: clients/javascript/rows.md - - Links: clients/javascript/links.md - - SQL: clients/javascript/sql_query.md - - Constants: clients/javascript/constants.md - - Examples: - - File Upload: clients/javascript/examples/file-upload.md - - Python: clients/python_api.md - - PHP: clients/php_api.md - - Ruby: clients/ruby_api.md -## open topics: -# social cards https://squidfunk.github.io/mkdocs-material/setup/setting-up-social-cards/ -# feedback integration: ...