From 22ef9426e589b4b9a7173f79be93b730cc25f644 Mon Sep 17 00:00:00 2001 From: loppi Date: Mon, 24 Mar 2025 17:06:31 +0700 Subject: [PATCH 01/37] [+] Add new methods to RequestManager interface #SCR-33 --- API/scripts.om.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 10f7372..13f0caa 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -565,6 +565,9 @@ export interface RequestManager { log(message: string, print?: boolean): this; logStatusMessage(message: string, print?: boolean): this; setStatusMessage(message: string): this; + getRequestId(): string | null; + getScriptName(): string | null; + getScriptLongId(): string | null; } export interface UserInfo { From 20e7d396e186e2a574bbd2ffcda6dc3ee5bffff7 Mon Sep 17 00:00:00 2001 From: loppi Date: Mon, 31 Mar 2025 12:36:37 +0700 Subject: [PATCH 02/37] [+] Added Clickhouse connector #SCR-32 --- API/clickhouse.md | 346 ++++++++++++++++++++++++++++++++++++++++++++ API/connectors.md | 26 ++-- API/scripts.om.d.ts | 85 +++++++++++ 3 files changed, 448 insertions(+), 9 deletions(-) create mode 100644 API/clickhouse.md diff --git a/API/clickhouse.md b/API/clickhouse.md new file mode 100644 index 0000000..5ac71c1 --- /dev/null +++ b/API/clickhouse.md @@ -0,0 +1,346 @@ +# Clickhouse + +## Интерфейс ClickhouseConnectorBuilder +```ts +interface ClickhouseConnectorBuilder { + setHost(value: string): this; + setPort(value: number): this; + setUsername(value: string): this; + setPassword(value: string): this; + setDatabase(value: string): this; + setHttps(value: boolean): this; + load(): ClickhouseConnection; +} +``` +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), базовый интерфейс [`коннекторов`](../appendix/glossary.md#connector) для настройки подключения к [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse). + +  + +```js +setHost(value: string): this; +``` +Устанавливает адрес подключения. Возвращает `this`. + +  + +```js +setPort(value: number): this; +``` +Устанавливает номер порта для подключения. Возвращает `this`. + +  + +```js +setUsername(value: string): this; +``` +Устанавливает имя пользователя. Возвращает `this`. + +  + +```js +setPassword(value: string): this; +``` +Устанавливает пароль. Возвращает `this`. + +  + +```js +setDatabase(value: string): this; +``` +Устанавливает имя базы данных. Возвращает `this`. + +  + +```js +setHttps(value: boolean): this; +``` +Устанавливает флаг https: выполнять подключение по https или http. Возвращает `this`. + +  + +```js +load(): ClickhouseConnection; +``` +Соединяется с БД и возвращает объект соединения [`ClickhouseConnection`](#clickhouse-connection). + +  + +## Интерфейс ClickhouseConnection +```ts +interface ClickhouseConnection { + qb(): ClickhouseQueryBuilder; +} +``` +Объект соединения с [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse). + +  + +```js +qb(): ClickhouseQueryBuilder; +``` +Возвращает интерфейс [`ClickhouseQueryBuilder`](#clickhosue-query-builder) построения запроса к базе данных. + +  + +## Интерфейс ClickhouseQueryBuilder +```ts +interface SqlQueryBuilder { + select(columns: string[]): this; + addSelect(column: string): this; + addSelectRaw(expression: string): this; + distinct(columns: string[]): this; + setFrom(table: string, alias: string | null): this; + setTable(table: string, alias: string | null): this; + where(column: string, operator: string | null, value: any[] | any, concatOperator: string): this; + orWhere(column: string, operator: string | null, value: any[] | any): this; + whereIn(column: string, values: any[], concatOperator: string, not: boolean): this; + whereBetween(column: string, minValue: string|number, maxValue: string|number, concatOperator: string, not: boolean): this; + orWhereBetween(column: string, minValue: string|number, maxValue: string|number): this; + groupBy(column: string): this; + orderBy(column: string, direction: string): this; + orderByDesc(column: string): this; + offset(value: number): this; + limit(value: number): this; + exists(): boolean; + count(): number; + sum(column: string): number; + insert(values: Object[] | Object): boolean; + get(): ClickhouseQueryResult; +} +``` +Интерфейс построения запроса к базе данных. + +  + +```js +select(columns: string[]): this; +``` +Устанавливает к выборке набор колонок `columns`, возвращает `this`. Заменяет установленные ранее колонки/выражения. +Значение `columns` по умолчанию - `['*']` - выбрать все колонки. + +  + +```js +addSelect(column: string): this; +``` +Добавляет к выборке колонку `column`, возвращает `this`. + +  + +```js +addSelectRaw(expression: string): this; +``` +Добавляет к выборке выражение `expression`, полезно для использования функций. Возвращает `this`. + +  + +```js +distinct(columns: string[]): this; +``` +Позволяет выбрать только уникальные строки для указанных колонок. Заменяет собой установленные ранее колонки вызовом метода `select`, возможно добавление колонок к выборке через `addSelect` или `addSelectRaw`. Возвращает `this`. + +  + +```js +setFrom(table: string, alias: string | null): this; +``` +Устанавливает таблицу, из которой будет производиться выборка. Для создания псевдонима используется `alias` Возвращает `this`. + +Вызов `setFrom('table1', 't1)` в запросе будет преобразован в `table1 AS t1`. + +  + +```js +setTable(table: string, alias: string | null): this; +``` +Алиас для метода `setFrom`. + +  + +```js +where(column: string, operator: string | null, value: any[] | any, concatOperator: string): this; +``` +Добавляет условие вида `column operator value`. + +Например, для вызова `where('columnName', '=', 5)` будет сформировано условие `columnName = 5`. + +Возможные значения `concatOprator`: `AND`, `OR`. По умолчанию `AND`. + +  + +```js +orWhere(column: string, operator: string | null, value: any[] | any): this; +``` +Аналогично вызову `where(column, operator, value, 'OR')` + +  + +```js +whereIn(column: string, values: any[], concatOperator: string, not: boolean): this; +``` +Добавляет условие с оператором `IN` вида `column IN (values[0], values[1], ...)`. Возвращает `this`. + +Возможные значения `concatOprator`: `AND`, `OR`. По умолчанию `AND`. + +Флаг `not` - отрицание операции, если `not=true`, то получим условие вида `column NOT IN (values[0], values[1], ...)`. По умолчанию `false`. + +  + +```js +whereBetween(column: string, minValue: string|number, maxValue: string|number, concatOperator: string, not: boolean): this; +``` +Добавляет условия с оператором `BETWEEN` вида `column BETWEEN minValue AND maxValue`. Возвращает `this`. + +Возможные значения `concatOprator`: `AND`, `OR`. По умолчанию `AND`. + +Флаг `not` - отрицание операции, если `not=true`, то получим условие вида `column NOT BETWEEN minValue AND maxValue`. По умолчанию `false`. + +  + +```js +orWhereBetween(column: string, minValue: string|number, maxValue: string|number): this; +``` +Аналогично вызову `whereBetween(column, minValue, maxValue, 'OR')`. + +  + +```js +groupBy(column: string): this; +``` +Устанавливает группировку по колонке `column`, возвращает `this`. + +  + +```js +orderBy(column: string, direction: string): this; +``` +Устанавливает сортировку по колонке `column`, направление задается аргументом `direction`, по умолчанию `ASC`, возвращает `this`. + +Возможные значения `direction`: `ASC`, `DESC`. + +  + +```js +orderByDesc(column: string): this; +``` +Устанавливает сортировку по убыванию по колонке `column`, возвращает `this`. + +  + +```js +offset(value: number): this; +``` +Устанавливает количество строк, которое надо пропустить, прежде чем начать выборку, возвращает `this`. + +  + +```js +limit(value: number): this; +``` +Устанавливает максимальное количество строк, которое надо выбрать, возвращает `this`. + +  + +```js +exists(): boolean; +``` +Проверяет наличие строк для сформированного запроса и возвращает `true`, если результат непустой, иначе `false`; + +  + +```js +count(): number; +``` +Возвращает количество найденных строк для сформированного запроса. + +  + +```js +sum(column: string): number; +``` +Суммирует значения колонки `column` и возвращает полученную сумму. + +  + +```js +insert(values: Object[] | Object): boolean; +``` +Вставляет значения `values` в таблицу, заданную методом `setFrom` или `setTable`. В случае успешности операции возвращает `true`, иначе `false`. + +Значения могут быть переданы в виде объекта: +```js +insert({id: 1, name: 'Apple', cnt: 10}) +``` +Или массива объектов: +```js +insert([ + {id: 1, name: 'Apple', cnt: 10}, + {id: 2, name: 'Orange', cnt: 15}, + {id: 3, name: 'Banana', cnt: 18}, + {id: 4, name: 'Peach', cnt: 12}, + {id: 5, name: 'Lemon', cnt: 15}, +]) +``` + + +  + +```js +get(): ClickhouseQueryResult; +``` +Конструирует SQL-запрос, передаёт его на исполнение в СУБД и возвращает интерфейс [`ClickhouseQueryResult`](#clickhouse-query-result) доступа к результатам запроса. + +  + +## Интерфейс ClickhouseQueryResult +```ts +interface ClickhouseQueryResult { + count(): number; + generator(): Object[]; + all(): Object[]; + first(): Object | null; + column(columnName: string): any[]; +} +``` +Интерфейс доступа к результатам запроса. + +  + +```js +count(): number; +``` +Возвращает количество выбранных строк. + +  + +```js +generator(): Object[]; +``` +Возвращает генератор для работы со строками запроса. + +  + +```js +all(): Object[]; +``` +Возвращает все выбранные строки. + +  + +```js +first(): Object | null; +``` +Возвращает первую строку запроса. + +  + +```js +column(columnName: string): any[]; +``` +Выбирает и возвращает в виде массива значения столбца `columnName`. + +  + +[API Reference](API.md) + +[Оглавление](../README.md) diff --git a/API/connectors.md b/API/connectors.md index 0b77ded..c35fb12 100644 --- a/API/connectors.md +++ b/API/connectors.md @@ -3,15 +3,16 @@ ## Интерфейс Connectors ```ts interface Connectors { - mysql(): MysqlConnectorBuilder; - postgresql(): PostgresqlConnectorBuilder; - sqlServer(): MicrosoftSqlConnectorBuilder; - oracle(): OracleConnectorBuilder; - snowflake(): SnowflakeConnectorBuilder; - mongodb(): Mongodb.ConnectorBuilder; - http(): Http.HttpManager; - winAgent(builtIn?: boolean): WinAgent.WinAgentBuilder; - verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder; + mysql(): MysqlConnectorBuilder; + postgresql(): PostgresqlConnectorBuilder; + sqlServer(): MicrosoftSqlConnectorBuilder; + oracle(): OracleConnectorBuilder; + snowflake(): SnowflakeConnectorBuilder; + mongodb(): Mongodb.ConnectorBuilder; + http(): Http.HttpManager; + winAgent(builtIn?: boolean): WinAgent.WinAgentBuilder; + verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder; + clickhouse(): ClickhouseConnectorBuilder } ``` Интерфейс, группирующий [`коннекторы`](../appendix/glossary.md#connector) к различным внешним системам. @@ -84,6 +85,13 @@ verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder;   +```js +clickhouse(): ClickhouseConnectorBuilder +``` +Возвращает коннектор [`ClickhouseConnectorBuilder`](./clickhouse.md#clickhouse-connector-builder) для подключения в базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse). + +  + [API Reference](API.md) [Оглавление](../README.md) diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 10f7372..0bca0e2 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -1667,6 +1667,90 @@ export interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { setProtocol(protocol: string): this; } +export interface ClickhouseConnectorBuilder { + setHost(value: string): this; + setPort(value: number): this; + setUsername(value: string): this; + setPassword(value: string): this; + setDatabase(value: string): this; + setHttps(value: boolean): this; + load(): ClickhouseConnection; +} + +export interface ClickhouseConnection { + qb(): ClickhouseQueryBuilder; +} + +export interface ClickhouseQueryBuilder { + /** + * @param columns Default is ['*'] + */ + select(columns: string[]): this; + addSelect(column: string): this; + addSelectRaw(expression: string): this; + /** + * @param columns Default is ['*'] + */ + distinct(columns: string[]): this; + /** + * @param table + * @param alias Default is null + */ + setFrom(table: string, alias: string | null): this; + /** + * @param table + * @param alias Default is null + */ + setTable(table: string, alias: string | null): this; + /** + * @param column + * @param value Default is null + * @param operator Default is null + * @param concatOperator 'AND'|'OR'; Default is 'AND' + */ + where(column: string, operator: string | null, value: any[] | any, concatOperator: string): this; + orWhere(column: string, operator: string | null, value: any[] | any): this; + /** + * @param column + * @param values + * @param concatOperator 'AND'|'OR'; Default is 'AND' + * @param not Default is false + */ + whereIn(column: string, values: any[], concatOperator: string, not: boolean): this; + /** + * @param column + * @param minValue + * @param maxValue + * @param concatOperator 'AND'|'OR'; Default is 'AND' + * @param not Default is false + */ + whereBetween(column: string, minValue: string|number, maxValue: string|number, concatOperator: string, not: boolean): this; + orWhereBetween(column: string, minValue: string|number, maxValue: string|number): this; + groupBy(column: string): this; + /** + * + * @param column + * @param direction 'asc'|'desc'; Default is 'asc' + */ + orderBy(column: string, direction: string): this; + orderByDesc(column: string): this; + offset(value: number): this; + limit(value: number): this; + exists(): boolean; + count(): number; + sum(column: string): number; + insert(values: Object[] | Object): boolean; + get(): ClickhouseQueryResult; +} + +export interface ClickhouseQueryResult { + count(): number; + generator(): Object[]; + all(): Object[]; + first(): Object | null; + column(columnName: string): any[]; +} + export interface Connectors { mysql(): MysqlConnectorBuilder; postgresql(): PostgresqlConnectorBuilder; @@ -1681,6 +1765,7 @@ export interface Connectors { */ winAgent(builtIn?: boolean): WinAgent.WinAgentBuilder; verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder; + clickhouse(): ClickhouseConnectorBuilder; } export namespace Notifications { From 831139c626f06633b9d1545338d6415075e9aa06 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Wed, 9 Apr 2025 20:47:42 +0700 Subject: [PATCH 03/37] - Formatting and correction --- API/clickhouse.md | 172 +++++++++++++++++++++++--------------------- API/connectors.md | 24 +++---- API/scripts.om.d.ts | 29 ++++---- 3 files changed, 115 insertions(+), 110 deletions(-) diff --git a/API/clickhouse.md b/API/clickhouse.md index 5ac71c1..fa9021e 100644 --- a/API/clickhouse.md +++ b/API/clickhouse.md @@ -3,16 +3,16 @@ ## Интерфейс ClickhouseConnectorBuilder ```ts interface ClickhouseConnectorBuilder { - setHost(value: string): this; - setPort(value: number): this; - setUsername(value: string): this; - setPassword(value: string): this; - setDatabase(value: string): this; - setHttps(value: boolean): this; - load(): ClickhouseConnection; + setHost(value: string): this; + setPort(value: number): this; + setUsername(value: string): this; + setPassword(value: string): this; + setDatabase(value: string): this; + setHttps(value: boolean): this; + load(): ClickhouseConnection; } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), базовый интерфейс [`коннекторов`](../appendix/glossary.md#connector) для настройки подключения к [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse). +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), базовый интерфейс [`коннекторов`](../appendix/glossary.md#connector) для настройки подключения к базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse).   @@ -54,7 +54,7 @@ setDatabase(value: string): this; ```js setHttps(value: boolean): this; ``` -Устанавливает флаг https: выполнять подключение по https или http. Возвращает `this`. +Устанавливает флаг `https`. Если он равен `true`, то выполнять подключение по протоколу [HTTPS](https://ru.wikipedia.org/wiki/HTTPS), иначе по протоколу [HTTP](https://ru.wikipedia.org/wiki/HTTP). Значение по умолчанию: `false`. Возвращает `this`.   @@ -68,44 +68,51 @@ load(): ClickhouseConnection; ## Интерфейс ClickhouseConnection ```ts interface ClickhouseConnection { - qb(): ClickhouseQueryBuilder; + qb(): ClickhouseQueryBuilder; } ``` -Объект соединения с [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse). +Объект соединения с базой данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse).   ```js qb(): ClickhouseQueryBuilder; ``` -Возвращает интерфейс [`ClickhouseQueryBuilder`](#clickhosue-query-builder) построения запроса к базе данных. +Возвращает интерфейс [`ClickhouseQueryBuilder`](#clickhouse-query-builder) построения запроса к базе данных.   ## Интерфейс ClickhouseQueryBuilder ```ts -interface SqlQueryBuilder { - select(columns: string[]): this; - addSelect(column: string): this; - addSelectRaw(expression: string): this; - distinct(columns: string[]): this; - setFrom(table: string, alias: string | null): this; - setTable(table: string, alias: string | null): this; - where(column: string, operator: string | null, value: any[] | any, concatOperator: string): this; - orWhere(column: string, operator: string | null, value: any[] | any): this; - whereIn(column: string, values: any[], concatOperator: string, not: boolean): this; - whereBetween(column: string, minValue: string|number, maxValue: string|number, concatOperator: string, not: boolean): this; - orWhereBetween(column: string, minValue: string|number, maxValue: string|number): this; - groupBy(column: string): this; - orderBy(column: string, direction: string): this; - orderByDesc(column: string): this; - offset(value: number): this; - limit(value: number): this; - exists(): boolean; - count(): number; - sum(column: string): number; - insert(values: Object[] | Object): boolean; - get(): ClickhouseQueryResult; +interface ClickhouseQueryBuilder { + select(columns?: string[]): this; + addSelect(column: string): this; + addSelectRaw(expression: string): this; + distinct(columns?: string[]): this; + + setFrom(table: string, alias?: string | null): this; + setTable(table: string, alias?: string | null): this; + + where(column: string, operator: string, value: any[] | any, concatOperator?: string): this; + orWhere(column: string, operator: string, value: any[] | any): this; + whereIn(column: string, values: any[], concatOperator?: string, not?: boolean): this; + whereBetween(column: string, minValue: string | number, maxValue: string | number, concatOperator?: string, not?: boolean): this; + orWhereBetween(column: string, minValue: string | number, maxValue: string | number): this; + + groupBy(column: string): this; + orderBy(column: string, direction?: string): this; + orderByDesc(column: string): this; + + offset(value: number): this; + limit(value: number): this; + + exists(): boolean; + count(): number; + sum(column: string): number; + + insert(values: Object[] | Object): boolean; + + get(): ClickhouseQueryResult; } ``` Интерфейс построения запроса к базе данных. @@ -113,17 +120,17 @@ interface SqlQueryBuilder {   ```js -select(columns: string[]): this; +select(columns?: string[]): this; ``` -Устанавливает к выборке набор колонок `columns`, возвращает `this`. Заменяет установленные ранее колонки/выражения. -Значение `columns` по умолчанию - `['*']` - выбрать все колонки. +Устанавливает к выборке набор колонок `columns`. Заменяет установленные ранее колонки/выражения. +Значение по умолчанию: `['*']` - выбрать все колонки. Возвращает `this`.   ```js addSelect(column: string): this; ``` -Добавляет к выборке колонку `column`, возвращает `this`. +Добавляет к выборке колонку `column`. Возвращает `this`.   @@ -135,116 +142,116 @@ addSelectRaw(expression: string): this;   ```js -distinct(columns: string[]): this; +distinct(columns?: string[]): this; ``` -Позволяет выбрать только уникальные строки для указанных колонок. Заменяет собой установленные ранее колонки вызовом метода `select`, возможно добавление колонок к выборке через `addSelect` или `addSelectRaw`. Возвращает `this`. +Позволяет выбрать только уникальные строки для указанных колонок `columns`. Заменяет собой установленные ранее колонки вызовом метода `select()`. Возможно добавление колонок к выборке через методы `addSelect()` или `addSelectRaw()`. Значение по умолчанию: `['*']` - выбрать все колонки. Возвращает `this`.   ```js -setFrom(table: string, alias: string | null): this; +setFrom(table: string, alias?: string | null): this; ``` -Устанавливает таблицу, из которой будет производиться выборка. Для создания псевдонима используется `alias` Возвращает `this`. +Устанавливает таблицу, из которой будет производиться выборка. Для создания псевдонима используется `alias` (по умолчанию: `null`). Возвращает `this`. -Вызов `setFrom('table1', 't1)` в запросе будет преобразован в `table1 AS t1`. +Вызов `setFrom('table1', 't1')` в запросе будет преобразован в `table1 AS t1`.   ```js -setTable(table: string, alias: string | null): this; +setTable(table: string, alias?: string | null): this; ``` -Алиас для метода `setFrom`. +Алиас для метода `setFrom()`.   ```js -where(column: string, operator: string | null, value: any[] | any, concatOperator: string): this; +where(column: string, operator: string, value: any[] | any, concatOperator?: string): this; ``` -Добавляет условие вида `column operator value`. +Добавляет условие вида `column operator value`. Возвращает `this`. Например, для вызова `where('columnName', '=', 5)` будет сформировано условие `columnName = 5`. -Возможные значения `concatOprator`: `AND`, `OR`. По умолчанию `AND`. +К ранее заданному условию добавляется через оператор конкатенации `concatOperator`. Допустимые значения: `AND`, `OR`. Значение по умолчанию: `AND`.   ```js -orWhere(column: string, operator: string | null, value: any[] | any): this; +orWhere(column: string, operator: string, value: any[] | any): this; ``` -Аналогично вызову `where(column, operator, value, 'OR')` +Аналогично вызову метода `where(column, operator, value, 'OR')`.   ```js -whereIn(column: string, values: any[], concatOperator: string, not: boolean): this; +whereIn(column: string, values: any[], concatOperator?: string, not?: boolean): this; ``` Добавляет условие с оператором `IN` вида `column IN (values[0], values[1], ...)`. Возвращает `this`. -Возможные значения `concatOprator`: `AND`, `OR`. По умолчанию `AND`. +К ранее заданному условию добавляется через оператор конкатенации `concatOperator`. Допустимые значения: `AND`, `OR`. Значение по умолчанию: `AND`. -Флаг `not` - отрицание операции, если `not=true`, то получим условие вида `column NOT IN (values[0], values[1], ...)`. По умолчанию `false`. +Флаг `not` устанавливает отрицание операции. Если значение `true`, то получим условие вида `column NOT IN (values[0], values[1], ...)`. Значение по умолчанию: `false`.   ```js -whereBetween(column: string, minValue: string|number, maxValue: string|number, concatOperator: string, not: boolean): this; +whereBetween(column: string, minValue: string | number, maxValue: string | number, concatOperator?: string, not?: boolean): this; ``` -Добавляет условия с оператором `BETWEEN` вида `column BETWEEN minValue AND maxValue`. Возвращает `this`. +Добавляет условие с оператором `BETWEEN` вида `column BETWEEN minValue AND maxValue`. Возвращает `this`. -Возможные значения `concatOprator`: `AND`, `OR`. По умолчанию `AND`. +К ранее заданному условию добавляется через оператор конкатенации `concatOperator`. Допустимые значения: `AND`, `OR`. Значение по умолчанию: `AND`. -Флаг `not` - отрицание операции, если `not=true`, то получим условие вида `column NOT BETWEEN minValue AND maxValue`. По умолчанию `false`. +Флаг `not` устанавливает отрицание операции. Если значение `true`, то получим условие вида `column NOT BETWEEN minValue AND maxValue`. Значение по умолчанию: `false`.   ```js -orWhereBetween(column: string, minValue: string|number, maxValue: string|number): this; +orWhereBetween(column: string, minValue: string | number, maxValue: string | number): this; ``` -Аналогично вызову `whereBetween(column, minValue, maxValue, 'OR')`. +Аналогично вызову метода `whereBetween(column, minValue, maxValue, 'OR')`.   ```js groupBy(column: string): this; ``` -Устанавливает группировку по колонке `column`, возвращает `this`. +Устанавливает группировку по колонке `column`. Возвращает `this`.   ```js -orderBy(column: string, direction: string): this; +orderBy(column: string, direction?: string): this; ``` -Устанавливает сортировку по колонке `column`, направление задается аргументом `direction`, по умолчанию `ASC`, возвращает `this`. +Устанавливает сортировку по колонке `column`. Возвращает `this`. -Возможные значения `direction`: `ASC`, `DESC`. +Направление сортировки задается аргументом `direction`. Допустимые значения: `ASC` - по возрастанию, `DESC` - по убыванию. Значение по умолчанию: `ASC`.   ```js orderByDesc(column: string): this; ``` -Устанавливает сортировку по убыванию по колонке `column`, возвращает `this`. +Устанавливает сортировку по убыванию по колонке `column`. Возвращает `this`.   ```js offset(value: number): this; ``` -Устанавливает количество строк, которое надо пропустить, прежде чем начать выборку, возвращает `this`. +Устанавливает количество строк, которое надо пропустить, прежде чем начать выборку. Возвращает `this`.   ```js limit(value: number): this; ``` -Устанавливает максимальное количество строк, которое надо выбрать, возвращает `this`. +Устанавливает максимальное количество строк в выборке. Возвращает `this`.   ```js exists(): boolean; ``` -Проверяет наличие строк для сформированного запроса и возвращает `true`, если результат непустой, иначе `false`; +Проверяет наличие строк для сформированного запроса. Возвращает `true`, если результат непустой, иначе `false`.   @@ -258,31 +265,30 @@ count(): number; ```js sum(column: string): number; ``` -Суммирует значения колонки `column` и возвращает полученную сумму. +Суммирует значения в колонке `column` и возвращает полученную сумму.   ```js insert(values: Object[] | Object): boolean; ``` -Вставляет значения `values` в таблицу, заданную методом `setFrom` или `setTable`. В случае успешности операции возвращает `true`, иначе `false`. +Вставляет значения `values` в таблицу, заданную методом `setTable()`. В случае успешного выполнения операции возвращает `true`, иначе `false`. -Значения могут быть переданы в виде объекта: +Значения могут быть переданы в виде объекта (ключом является название колонки): ```js -insert({id: 1, name: 'Apple', cnt: 10}) +insert({id: 1, name: 'Apple', count: 10}) ``` Или массива объектов: ```js insert([ - {id: 1, name: 'Apple', cnt: 10}, - {id: 2, name: 'Orange', cnt: 15}, - {id: 3, name: 'Banana', cnt: 18}, - {id: 4, name: 'Peach', cnt: 12}, - {id: 5, name: 'Lemon', cnt: 15}, + {id: 1, name: 'Apple', count: 10}, + {id: 2, name: 'Orange', count: 15}, + {id: 3, name: 'Banana', count: 18}, + {id: 4, name: 'Peach', count: 12}, + {id: 5, name: 'Lemon', count: 15}, ]) ``` -   ```js @@ -295,11 +301,11 @@ get(): ClickhouseQueryResult; ## Интерфейс ClickhouseQueryResult ```ts interface ClickhouseQueryResult { - count(): number; - generator(): Object[]; - all(): Object[]; - first(): Object | null; - column(columnName: string): any[]; + count(): number; + generator(): IterableIterator; + all(): Object[]; + first(): Object | null; + column(columnName: string): any[]; } ``` Интерфейс доступа к результатам запроса. @@ -314,7 +320,7 @@ count(): number;   ```js -generator(): Object[]; +generator(): IterableIterator; ``` Возвращает генератор для работы со строками запроса. diff --git a/API/connectors.md b/API/connectors.md index c35fb12..99583b1 100644 --- a/API/connectors.md +++ b/API/connectors.md @@ -3,16 +3,16 @@ ## Интерфейс Connectors ```ts interface Connectors { - mysql(): MysqlConnectorBuilder; - postgresql(): PostgresqlConnectorBuilder; - sqlServer(): MicrosoftSqlConnectorBuilder; - oracle(): OracleConnectorBuilder; - snowflake(): SnowflakeConnectorBuilder; - mongodb(): Mongodb.ConnectorBuilder; - http(): Http.HttpManager; - winAgent(builtIn?: boolean): WinAgent.WinAgentBuilder; - verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder; - clickhouse(): ClickhouseConnectorBuilder + mysql(): MysqlConnectorBuilder; + postgresql(): PostgresqlConnectorBuilder; + sqlServer(): MicrosoftSqlConnectorBuilder; + oracle(): OracleConnectorBuilder; + snowflake(): SnowflakeConnectorBuilder; + mongodb(): Mongodb.ConnectorBuilder; + http(): Http.HttpManager; + winAgent(builtIn?: boolean): WinAgent.WinAgentBuilder; + verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder; + clickhouse(): ClickhouseConnectorBuilder; } ``` Интерфейс, группирующий [`коннекторы`](../appendix/glossary.md#connector) к различным внешним системам. @@ -86,9 +86,9 @@ verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder;   ```js -clickhouse(): ClickhouseConnectorBuilder +clickhouse(): ClickhouseConnectorBuilder; ``` -Возвращает коннектор [`ClickhouseConnectorBuilder`](./clickhouse.md#clickhouse-connector-builder) для подключения в базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse). +Возвращает коннектор [`ClickhouseConnectorBuilder`](./clickhouse.md#clickhouse-connector-builder) для подключения к базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse).   diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 0bca0e2..c89d1e2 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -1685,38 +1685,38 @@ export interface ClickhouseQueryBuilder { /** * @param columns Default is ['*'] */ - select(columns: string[]): this; + select(columns?: string[]): this; addSelect(column: string): this; addSelectRaw(expression: string): this; /** * @param columns Default is ['*'] */ - distinct(columns: string[]): this; + distinct(columns?: string[]): this; /** * @param table * @param alias Default is null */ - setFrom(table: string, alias: string | null): this; + setFrom(table: string, alias?: string | null): this; /** * @param table * @param alias Default is null */ - setTable(table: string, alias: string | null): this; + setTable(table: string, alias?: string | null): this; /** * @param column - * @param value Default is null - * @param operator Default is null + * @param operator + * @param value * @param concatOperator 'AND'|'OR'; Default is 'AND' */ - where(column: string, operator: string | null, value: any[] | any, concatOperator: string): this; - orWhere(column: string, operator: string | null, value: any[] | any): this; + where(column: string, operator: string, value: any[] | any, concatOperator?: string): this; + orWhere(column: string, operator: string, value: any[] | any): this; /** * @param column * @param values * @param concatOperator 'AND'|'OR'; Default is 'AND' * @param not Default is false */ - whereIn(column: string, values: any[], concatOperator: string, not: boolean): this; + whereIn(column: string, values: any[], concatOperator?: string, not?: boolean): this; /** * @param column * @param minValue @@ -1724,15 +1724,14 @@ export interface ClickhouseQueryBuilder { * @param concatOperator 'AND'|'OR'; Default is 'AND' * @param not Default is false */ - whereBetween(column: string, minValue: string|number, maxValue: string|number, concatOperator: string, not: boolean): this; - orWhereBetween(column: string, minValue: string|number, maxValue: string|number): this; + whereBetween(column: string, minValue: string | number, maxValue: string | number, concatOperator?: string, not?: boolean): this; + orWhereBetween(column: string, minValue: string | number, maxValue: string | number): this; groupBy(column: string): this; /** - * * @param column - * @param direction 'asc'|'desc'; Default is 'asc' + * @param direction 'ASC'|'DESC'; Default is 'ASC' */ - orderBy(column: string, direction: string): this; + orderBy(column: string, direction?: string): this; orderByDesc(column: string): this; offset(value: number): this; limit(value: number): this; @@ -1745,7 +1744,7 @@ export interface ClickhouseQueryBuilder { export interface ClickhouseQueryResult { count(): number; - generator(): Object[]; + generator(): IterableIterator; all(): Object[]; first(): Object | null; column(columnName: string): any[]; From aa896cb80ead61d2342a714d022075835a1c66e1 Mon Sep 17 00:00:00 2001 From: loppi Date: Thu, 10 Apr 2025 12:36:59 +0700 Subject: [PATCH 04/37] [+] Added Cell.dropDownSelector() method #SCR-38 --- API/scripts.om.d.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 10f7372..0086aff 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -25,6 +25,21 @@ export interface Cell { dropDown(): Labels; getFormatType(): string; isEditable(): boolean; + dropDownSelector(): DropDownSelector; +} + +export interface DropDownSelector { + totalCount(): number; + /** + * @param chunkSize Default: 1000 + */ + generator(chunkSize: number | null): IterableIterator; +} + +export interface DropDownSelectorChunk { + start(): number; + count(): number; + all(): Label[]; } export interface Cells { From 5fbbb8c314e4362829e73932f15bd2ff7d60ba6f Mon Sep 17 00:00:00 2001 From: Ruslan Abdullaev Date: Tue, 15 Apr 2025 19:06:54 +0500 Subject: [PATCH 05/37] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B4=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=BA=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20?= =?UTF-8?q?=D1=81=20=D1=81=D0=B5=D0=BA=D1=80=D0=B5=D1=82=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API/apiSecrets.md | 198 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 API/apiSecrets.md diff --git a/API/apiSecrets.md b/API/apiSecrets.md new file mode 100644 index 0000000..4d16709 --- /dev/null +++ b/API/apiSecrets.md @@ -0,0 +1,198 @@ +# Поддержка работы с секретами + +Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом [OpenBao](https://openbao.org/), который поставляется с дистрибутивом. У сервиса есть UI. Через него клиенты записывают в защищенное хранилище важные данные, чтобы затем использовать их в скриптах. + +Такие данные называются секретами. Они бывают разных видов. Поддерживаемые типы: +- OpenBaoKeyValueSecret (ключ-значение). + +## Quick start + +Посмотрим на пример получения секрета из хранилища и разберем, что здесь происходит: + +```typescript + +let secret = om.secrets.getStorage('openbao-vault').getSecret('it-gets-secret-path', 'it-gets-secret-key') + +``` + +Интерфейс ***secrets** представляет метод `getStorage()`. В него передается идентификатор хранилища. Доступы к ним настраиваются в манифесте проекта администраторами. + +К одному проекту могут быть подключены сразу несколько хранилищ. В одном скрипте можно обращаться сразу к нескольким. + +```typescript + +let baoSecret = om.secrets.getStorage('openbao-vault').getSecret( + 'several-vaults-single-script-openbao-path', + 'several-vaults-single-script-openbao-key' +) + +let hashiSecret = om.secrets.getStorage('hashicorp-vault').getSecret( + 'several-vaults-single-script-hashicorp-path', + 'several-vaults-single-script-hashicorp-key' +) +``` + +Поддерживаются решения [OpenBao](https://openbao.org/) и [Hashicorp Vault](https://www.vaultproject.io/). Список подключенных хранилищ можно посмотреть в админке в разделе `/secrets`. + +### Структура секретов + +Секреты в хранилищах хранятся в иерархиях. + +``` +/secret/path/to/secrets +|--- secret-key-1 +|--- secret-key-2 +``` + +Найти секрет в хранилище можно, зная "папку", в которой он лежит (путь) и название самого секрета (ключ). Поэтому метод `getSecret()`, доступный в объекте хранилища, принимает в качестве аргумента два этих параметра. + +Обратившись к хранилищу за секретом, вы получите объект Secret. Его можно преобразовать в JSON-объект и работать с ним обычным способом. + +```typescript + +console.log(secret.toJson().type); +console.log(secret.toJson().params.key); +console.log(secret.toJson().params.value); + +{ + "type":"OpenBaoKeyValueSecret", + "params": { + "key":"it-gets-secret-key", + "path":"it-gets-secret-path" + } +} +``` + +Обратите внимание, что значения секрета в объекте нет. Так сделано для безопасности. Значения раскрываются только на backend'е. Передавайте в скриптах в доступные методы API объекты секретов. Backend сам сделает запрос в хранилище и подставит значение секрета, где это нужно. + +```typescript + +let secret = om.secrets.getStorage('openbao-vault').getSecret('ftp-adapter-path', 'ftp-adapter-port') + +let fs = om.filesystems.ftp(); + +fs.setPort(secret); +``` + +Не обязательно явно забирать секрет из хранилища, чтобы передать его на backend. Можно самостоятельно собрать неклассифицированный объект V8 и воспользоваться им. + +```typescript + +const NCSecret = { + "type": "OpenBaoKeyValueSecret", + "params": { + "storageIdentifier": "openbao-vault", + "path": "nc-secret-port-path", + "key": "nc-secret-port-key" + } +} + +let fs = om.filesystems.ftp(); +fs.setPort(NCSecret); +``` + +Валидация по-прежнему работает в методах API, даже на значениях секретов. Если тип значения не совпадет с тем, который ожидается методом API, вылетит ошибка. + +```typescript +let secret = om.secrets.getStorage('openbao-vault').getSecret('ftp-bad-secret-path', 'ftp-bad-secret-port') + +let fs = om.filesystems.ftp(); + +fs.setPort(secret); +console.log(fs.getPort()); +``` + +### Use-кейсы + +Объекты секретов можно передавать в качестве env-параметров из одних скриптов в другие + +```typescript +const targetScript = om.common.resultInfo() + .actionsInfo() + .makeMacrosAction('testChainEnvironment_2'); + +const envSecret = om.secrets.getStorage('openbao-vault').getSecret('env-chainset-path', 'env-chainset-key') + +targetScript.environmentInfo().set('ENV_SECRET', envSecret); +targetScript.appendAfter(); +``` + +### Интерфейсы и изменения в API + +Интерфейсы функционала описаны в декларации + +```typescript +export interface Secrets { + getStorage(vaultId: string): SecretStorage; +} + +export interface SecretStorage { + getSecret(path: string, key: string): SecretValue +} + +export interface SecretValue { + getStorageIdentifier(): string; + getIdentifier(): string; + toJson(): object; +} +``` + +В некоторые методы, добавлена поддержка секретов. Прежний код, передающий в них скалярные аргрументы, должен работать без изменений. У вас появляется выбор - можно использовать и скалярные типы, и секреты. Опознать методы с новыми возможностями можно по изменениям в сигнатурах. + +```typescript + +export interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { + setAccount(account: string|SecretValue): SnowflakeConnectorBuilder; + + setRegion(region: string|SecretValue): SnowflakeConnectorBuilder; + + // ... +} +``` + +Объект SecretValue можно передавать в методы: + +- FTPAdapter.setHost +- FTPAdapter.setPort +- FTPAdapter.setUsername +- FTPAdapter.setPassword +- SqlConnectorBuilder.setHost +- SqlConnectorBuilder.setPort +- SqlConnectorBuilder.setUsername +- SqlConnectorBuilder.setPassword +- SqlConnectorBuilder.setDatabase +- SqlConnectorBuilder.setHost +- OracleConnectorBuilder.setServiceName +- OracleConnectorBuilder.setSchema +- OracleConnectorBuilder.setTNS +- PostgresqlImportBuilder.setSchema +- SnowflakeConnectorBuilder.setAccount +- SnowflakeConnectorBuilder.setRegion +- SnowflakeConnectorBuilder.setSchema +- SnowflakeConnectorBuilder.setWarehouse +- SnowflakeConnectorBuilder.setRole +- Mongodb.ConnectorBuilder.setDSN +- Mongodb.ConnectorBuilder.setDatabase +- SqlBulkCopyBuilder.setServerName +- SqlBulkCopyBuilder.setPort +- SqlBulkCopyBuilder.setUsername +- SqlBulkCopyBuilder.setPassword +- SqlBulkCopyBuilder.setDatabase +- Http.Url.setUrl +- Http.Url.setUrlPath +- Http.Url.setUrlScheme +- Http.Url.setHost +- Http.Url.setPort +- Http.Url.setFragment +- Http.Url.setUser +- Http.Url.setPassword +- Http.HttpAuth.setUser +- Http.HttpAuth.setPassword +- Http.Params.setAll +- Http.Params.set +- Http.Cert.setPassphrase +- Crypto.sha1 +- Crypto.hash +- Crypto.hmac +- WinAgentBuilder.setCommandUrl +- WinAgentBuilder.setDownloadUrl From 2203b15af97355e01170a199d81e03a47b461cf3 Mon Sep 17 00:00:00 2001 From: Ruslan Abdullaev Date: Tue, 15 Apr 2025 20:10:30 +0500 Subject: [PATCH 06/37] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=B5=D0=BA=D0=BB=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API/apiSecrets.md | 47 ----------------------- API/scripts.om.d.ts | 93 +++++++++++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 85 deletions(-) diff --git a/API/apiSecrets.md b/API/apiSecrets.md index 4d16709..1a7954a 100644 --- a/API/apiSecrets.md +++ b/API/apiSecrets.md @@ -149,50 +149,3 @@ export interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { // ... } ``` - -Объект SecretValue можно передавать в методы: - -- FTPAdapter.setHost -- FTPAdapter.setPort -- FTPAdapter.setUsername -- FTPAdapter.setPassword -- SqlConnectorBuilder.setHost -- SqlConnectorBuilder.setPort -- SqlConnectorBuilder.setUsername -- SqlConnectorBuilder.setPassword -- SqlConnectorBuilder.setDatabase -- SqlConnectorBuilder.setHost -- OracleConnectorBuilder.setServiceName -- OracleConnectorBuilder.setSchema -- OracleConnectorBuilder.setTNS -- PostgresqlImportBuilder.setSchema -- SnowflakeConnectorBuilder.setAccount -- SnowflakeConnectorBuilder.setRegion -- SnowflakeConnectorBuilder.setSchema -- SnowflakeConnectorBuilder.setWarehouse -- SnowflakeConnectorBuilder.setRole -- Mongodb.ConnectorBuilder.setDSN -- Mongodb.ConnectorBuilder.setDatabase -- SqlBulkCopyBuilder.setServerName -- SqlBulkCopyBuilder.setPort -- SqlBulkCopyBuilder.setUsername -- SqlBulkCopyBuilder.setPassword -- SqlBulkCopyBuilder.setDatabase -- Http.Url.setUrl -- Http.Url.setUrlPath -- Http.Url.setUrlScheme -- Http.Url.setHost -- Http.Url.setPort -- Http.Url.setFragment -- Http.Url.setUser -- Http.Url.setPassword -- Http.HttpAuth.setUser -- Http.HttpAuth.setPassword -- Http.Params.setAll -- Http.Params.set -- Http.Cert.setPassphrase -- Crypto.sha1 -- Crypto.hash -- Crypto.hmac -- WinAgentBuilder.setCommandUrl -- WinAgentBuilder.setDownloadUrl diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 10f7372..f879eca 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -787,20 +787,20 @@ export interface BinaryData { } export interface Crypto { - sha1(data: string): string; + sha1(data: string|SecretValue): string; /** * * @param algo available values can be retrieved by getHashAlgorithms() * @param binary defaults to false */ - hash(algo: string, data: string, binary?: boolean): string | BinaryData; + hash(algo: string , data: string|SecretValue , binary?: boolean): string | BinaryData /** * * @param algo available values can be retrieved by getHmacHashAlgorithms() * @param binary defaults to false */ - hmac(algo: string, data: string, key: string | BinaryData, binary?: boolean): string | BinaryData; + hmac(algo: string, data: string|SecretValue, key: string | BinaryData, binary?: boolean): string | BinaryData; getHashAlgorithms(): string[]; getHmacAlgorithms(): string[]; @@ -872,16 +872,16 @@ export interface BaseAdapter { } export interface FTPAdapter extends BaseAdapter { - setHost(host: string): this; + setHost(host: string|SecretValue): this; getHost(): string; - setPort(port: number): this; + setPort(port: number|SecretValue): this; getPort(): number; - setUsername(username: string): this; + setUsername(username: string|SecretValue): this getUsername(): string | null; - setPassword(password: string): this; + setPassword(password: string|SecretValue): this; getPassword(): string | null; setRoot(root: string): this; @@ -981,11 +981,11 @@ export interface SqlConnection { } export interface SqlConnectorBuilder { - setHost(value: string): this; - setPort(value: number): this; - setUsername(value: string): this; - setPassword(value: string): this; - setDatabase(value: string): this; + setHost(value: string|SecretValue): this; + setPort(value: number|SecretValue): this; + setUsername(value: string|SecretValue): this; + setPassword(value: string|SecretValue): this; + setDatabase(value: string|SecretValue): this; load(): SqlConnection; } @@ -1001,31 +1001,31 @@ export interface SqlBulkCopyBuilder { * -S * @param value */ - setServerName(value: string): this; + setServerName(value: string|SecretValue): this; /** * Port is part of server name * @param value */ - setPort(value: number): this; + setPort(value: number|SecretValue): this; /** * -U * @param value */ - setUsername(value: string): this; + setUsername(value: string|SecretValue): this; /** * -P * @param value */ - setPassword(value: string): this; + setPassword(value: string|SecretValue): this; /** * -d * @param value */ - setDatabase(value: string): this; + setDatabase(value: string|SecretValue): this; /** * Query for export or table query string for import @@ -1211,9 +1211,9 @@ export interface OracleImportBuilder { } export interface OracleConnectorBuilder extends SqlConnectorBuilder { - setServiceName(value: string): this; - setSchema(value: string): this; - setTNS(value: string): this; + setServiceName(value: string|SecretValue): this; + setSchema(value: string|SecretValue): this; + setTNS(value: string|SecretValue): this loadImportBuilder(): OracleImportBuilder; } @@ -1323,8 +1323,8 @@ export namespace Mongodb { } export interface ConnectorBuilder { - setDSN(value: string): this; - setDatabase(value: string): this; + setDSN(value: string|SecretValue): this; + setDatabase(value: string|SecretValue): this; load(): Connection; } } @@ -1391,28 +1391,28 @@ export namespace Http { } export interface Url { - setUrl(url: string): boolean; + setUrl(url: string|SecretValue): boolean; getUrl(): string; - setUrlPath(path: string): boolean; + setUrlPath(path: string|SecretValue): boolean; getUrlPath(): string; - setUrlScheme(scheme: string): boolean; + setUrlScheme(scheme: string|SecretValue): boolean; getUrlScheme(): string; - setHost(host: string): boolean; + setHost(host: string|SecretValue): boolean; getHost(): string; - setPort(port: number | string): boolean; + setPort(port: number | string | SecretValue): boolean; getPort(): number | null; - setUser(user: string): boolean; + setUser(user: string|SecretValue): boolean; getUser(): string | null; - setPassword(password: string): boolean; + setPassword(password: string|SecretValue): boolean getPassword(): string | null; - setFragment(fragment: string): boolean; + setFragment(fragment: string|SecretValue): boolean; getFragment(): string | null; params(): UrlParams; @@ -1454,8 +1454,8 @@ export namespace Http { } export interface HttpAuth { - setUser(user: string): this; - setPassword(password: string): this; + setUser(user: string|SecretValue): this; + setPassword(password: string|SecretValue): this; /** * @param type basic|digest|ntlm */ @@ -1584,8 +1584,8 @@ export namespace WinAgent { } export interface WinAgentBuilder { - setCommandUrl(url: string): this; - setDownloadUrl(url: string): this; + setCommandUrl(url: string|SecretValue): this; + setDownloadUrl(url: string|SecretValue): this; auth(): Http.HttpAuth; setConnectTimeout(sec: number): this; setRequestTimeout(sec: number): this; @@ -1632,6 +1632,7 @@ export interface MysqlImportBuilder { export interface PostgresqlImportBuilder { setTable(name: string): this; setSchema(name: string): this; + setSchema(name: string|SecretValue): this; setDelimiter(delimiter: string): this; setEnclosure(enclosure: string): this; setEscape(escape: string): this; @@ -1654,16 +1655,17 @@ export interface PgsqlDrivenVerticaConnectorBuilder extends PostgresqlConnectorB export interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { setAccount(account: string): this; - setRegion(region: string): this; + setAccount(account: string|SecretValue): this; + setRegion(region: string|SecretValue): this; /** * Configuring OCSP Checking * Default is false * @param insecure */ setInsecure(insecure: boolean): this; - setWarehouse(warehouse: string): this; - setSchema(schema: string): this; - setRole(role: string): this; + setWarehouse(warehouse: string|SecretValue): this; + setSchema(schema: string|SecretValue): this; + setRole(role: string|SecretValue): this; setProtocol(protocol: string): this; } @@ -1775,6 +1777,20 @@ export interface WorkspaceUsersTab extends Tab { export interface ModelUsersTab extends Tab { } +export interface Secrets { + getStorage(vaultId: string): SecretStorage; +} + +export interface SecretStorage { + getSecret(path: string, key: string): SecretValue +} + +export interface SecretValue { + getStorageIdentifier(): string; + getIdentifier(): string; + toJson(): object; +} + export interface OM { readonly common: Common; readonly environment: Environment; @@ -1791,6 +1807,7 @@ export interface OM { readonly audit: Audit; readonly crypto: Crypto; readonly users: Users; + readonly secrets: Secrets; } export var om: OM; From 15215ddf171f1448220ee9e9043aafacd3065589 Mon Sep 17 00:00:00 2001 From: Ruslan Abdullaev Date: Tue, 15 Apr 2025 20:29:13 +0500 Subject: [PATCH 07/37] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE=20=D1=81?= =?UTF-8?q?=D0=B5=D0=BA=D1=80=D0=B5=D1=82=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API/apiSecrets.md | 25 +++++++++++-------------- API/connectors.md | 7 +++++++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/API/apiSecrets.md b/API/apiSecrets.md index 1a7954a..35d5792 100644 --- a/API/apiSecrets.md +++ b/API/apiSecrets.md @@ -1,8 +1,8 @@ # Поддержка работы с секретами -Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом [OpenBao](https://openbao.org/), который поставляется с дистрибутивом. У сервиса есть UI. Через него клиенты записывают в защищенное хранилище важные данные, чтобы затем использовать их в скриптах. +Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом [OpenBao](https://openbao.org/), который поставляется с дистрибутивом. У сервиса есть UI (/openbao/ui). Через него клиенты записывают в защищенное хранилище важные данные, чтобы затем использовать в скриптах. -Такие данные называются секретами. Они бывают разных видов. Поддерживаемые типы: +Такие данные называются **секретами**. Они бывают разных видов. Поддерживаемые типы: - OpenBaoKeyValueSecret (ключ-значение). ## Quick start @@ -10,17 +10,14 @@ Посмотрим на пример получения секрета из хранилища и разберем, что здесь происходит: ```typescript - let secret = om.secrets.getStorage('openbao-vault').getSecret('it-gets-secret-path', 'it-gets-secret-key') - ``` -Интерфейс ***secrets** представляет метод `getStorage()`. В него передается идентификатор хранилища. Доступы к ним настраиваются в манифесте проекта администраторами. +Интерфейс **secrets** представляет метод `getStorage()`. В него передается идентификатор хранилища. Доступ к нему настраивается в манифесте workspace'а администраторами. -К одному проекту могут быть подключены сразу несколько хранилищ. В одном скрипте можно обращаться сразу к нескольким. +К одному workspace'у могут быть подключены сразу несколько хранилищ. В скрипте можно обращаться к любому из них или сразу к нескольким. ```typescript - let baoSecret = om.secrets.getStorage('openbao-vault').getSecret( 'several-vaults-single-script-openbao-path', 'several-vaults-single-script-openbao-key' @@ -44,9 +41,9 @@ let hashiSecret = om.secrets.getStorage('hashicorp-vault').getSecret( |--- secret-key-2 ``` -Найти секрет в хранилище можно, зная "папку", в которой он лежит (путь) и название самого секрета (ключ). Поэтому метод `getSecret()`, доступный в объекте хранилища, принимает в качестве аргумента два этих параметра. +Найти секрет можно, зная "папку", в которой он лежит (путь) и название самого секрета (ключ). Поэтому методу `getSecret()` нужны в качестве аргументов оба параметра. -Обратившись к хранилищу за секретом, вы получите объект Secret. Его можно преобразовать в JSON-объект и работать с ним обычным способом. +Обратившись к хранилищу за секретом, вы получите объект SecretValue. Его можно преобразовать в JSON-объект и работать с ним обычным способом. ```typescript @@ -63,10 +60,9 @@ console.log(secret.toJson().params.value); } ``` -Обратите внимание, что значения секрета в объекте нет. Так сделано для безопасности. Значения раскрываются только на backend'е. Передавайте в скриптах в доступные методы API объекты секретов. Backend сам сделает запрос в хранилище и подставит значение секрета, где это нужно. +Обратите внимание, что значения секрета в объекте нет. Так сделано для безопасности. Значения раскрываются только на backend'е. Передавайте в доступные методы API объекты секретов. Backend сам сделает запрос в хранилище и подставит значение секрета, где это нужно. ```typescript - let secret = om.secrets.getStorage('openbao-vault').getSecret('ftp-adapter-path', 'ftp-adapter-port') let fs = om.filesystems.ftp(); @@ -77,7 +73,6 @@ fs.setPort(secret); Не обязательно явно забирать секрет из хранилища, чтобы передать его на backend. Можно самостоятельно собрать неклассифицированный объект V8 и воспользоваться им. ```typescript - const NCSecret = { "type": "OpenBaoKeyValueSecret", "params": { @@ -104,7 +99,7 @@ console.log(fs.getPort()); ### Use-кейсы -Объекты секретов можно передавать в качестве env-параметров из одних скриптов в другие +Объекты секретов можно передавать в качестве env-параметров из одних скриптов в другие. ```typescript const targetScript = om.common.resultInfo() @@ -137,7 +132,9 @@ export interface SecretValue { } ``` -В некоторые методы, добавлена поддержка секретов. Прежний код, передающий в них скалярные аргрументы, должен работать без изменений. У вас появляется выбор - можно использовать и скалярные типы, и секреты. Опознать методы с новыми возможностями можно по изменениям в сигнатурах. +В некоторые методы, добавлена поддержка секретов. Прежний код, передающий в них скалярные аргументы, должен работать без изменений. + +У вас появляется выбор - можно использовать и скалярные типы, и секреты. Опознать методы с новыми возможностями можно по изменениям в сигнатурах. ```typescript diff --git a/API/connectors.md b/API/connectors.md index 0b77ded..0c5497f 100644 --- a/API/connectors.md +++ b/API/connectors.md @@ -84,6 +84,13 @@ verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder;   +```js +secrets(): SecretStorage; +``` +Возвращает объект для взаимодействия с [защищенным хранилищем секретов](./apiSecrets.md) + +  + [API Reference](API.md) [Оглавление](../README.md) From 7822db52a56ecd6842e2015fba274dddca260d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BE=D0=B2?= Date: Mon, 28 Apr 2025 14:44:16 +0300 Subject: [PATCH 08/37] file separation --- API/views.md | 389 ++------------------------------------------------- 1 file changed, 8 insertions(+), 381 deletions(-) diff --git a/API/views.md b/API/views.md index 5294b54..12249b7 100644 --- a/API/views.md +++ b/API/views.md @@ -113,7 +113,7 @@ cleanCellsData(cubesIdentifiers?: number[]): this; ```js cubeCellSelector(identifier: string | number): CubeCellSelectorBuilder; ``` -Возвращает интерфейс [`CubeCellSelectorBuilder`](./cubeCell.md#cube-cell-selector-builder) выборки клеток для куба `identifier`. `identifier` должен быть именем или [`longId`](#long-id) куба. При указании некорректного `identifier` выбрасывается исключение. +Возвращает интерфейс [`CubeCellSelectorBuilder`](./cubeCell.md#cube-cell-selector-builder) выборки клеток для куба `identifier`. `identifier` должен быть именем или [`longId`](./readingGrid.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение.   @@ -121,14 +121,14 @@ cubeCellSelector(identifier: string | number): CubeCellSelectorBuilder; cubeCellUpdater(identifier: string | number): CubeCellUpdaterBuilder; ``` -Возвращает интерфейс [`CubeCellUpdaterBuilder`](./cubeCell.md#cube-cell-updater-builder) обновления клеток куба с именем или идентификатором `identifier` по формуле. `identifier` должен быть именем или [`longId`](#long-id) куба. При указании некорректного `identifier` выбрасывается исключение. +Возвращает интерфейс [`CubeCellUpdaterBuilder`](./cubeCell.md#cube-cell-updater-builder) обновления клеток куба с именем или идентификатором `identifier` по формуле. `identifier` должен быть именем или [`longId`](./readingGrid.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение.   ```js getCubeInfo(identifier: string | number): CubeInfo; ``` -Возвращает интерфейс [`CubeInfo`](./cubeCell.md#cube-info) для получения информации о кубе `identifier`. `identifier` должен быть именем или [`longId`](#long-id) куба. При указании некорректного `identifier` выбрасывается исключение. +Возвращает интерфейс [`CubeInfo`](./cubeCell.md#cube-info) для получения информации о кубе `identifier`. `identifier` должен быть именем или [`longId`](./readingGrid.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение.   @@ -217,9 +217,9 @@ rowsFilter(data: string | string[] | number | number[]): this; `string[]` — массив названий строк; -`number` — [`longId`](#long-id) строки; +`number` — [`longId`](./readingGrid.md#long-id) строки; -`number[]` — массив [`longId`](#long-id) строк. +`number[]` — массив [`longId`](./readingGrid.md#long-id) строк. Возвращает `this`. @@ -235,7 +235,7 @@ columnsFilter(data: string | string[] | number | number[]): this; ```js withoutValues(): this; ``` -Устанавливает признак загрузки с сервера данных без значений ячеек. В этом случае функции интерфейса [`Cell`](#cell) [`getValue()`](#cell.get-value), [`getNativeValue()`](#cell.get-native-value) и [`getContextValue()`](#get-context-value) будут возвращать `null`, однако функции [`Cell`](#cell).[`setValue()`](#cell.set-value), [`Cells`](#cells).[`setValue()`](#cells.set-value) и [`CellBuffer`](./common.md#cell-buffer).[`apply()`](./common.md#cell-buffer.apply) не теряют свою магическую силу. Возвращает `this`. +Устанавливает признак загрузки с сервера данных без значений ячеек. В этом случае функции интерфейса [`Cell`](./readingGrid.md#cell) [`getValue()`](./readingGrid.md#cell.get-value), [`getNativeValue()`](./readingGrid.md#cell.get-native-value) и [`getContextValue()`](./readingGrid.md#get-context-value) будут возвращать `null`, однако функции [`Cell`](./readingGrid.md#cell).[`setValue()`](./readingGrid.md#cell.set-value), [`Cells`](./readingGrid.md#cells).[`setValue()`](./readingGrid.md#cells.set-value) и [`CellBuffer`](./common.md#cell-buffer).[`apply()`](./common.md#cell-buffer.apply) не теряют свою магическую силу. Возвращает `this`. Эта функция существенно ускоряет работу в тех случаях, когда нужно записать данные, но не читать их. @@ -245,11 +245,11 @@ withoutValues(): this; ```js addDependentContext(identifier: number): this; ``` -Добавляет в фильтр по строкам весь зависимый контекст переданного [`longId`](#long-id) `identifier`: материнские и дочерние элементы всех уровней. +Добавляет в фильтр по строкам весь зависимый контекст переданного [`longId`](./readingGrid.md#long-id) `identifier`: материнские и дочерние элементы всех уровней. Если эта функция многократно вызывается с аргументами, один из которых является потомком остальных (порядок вызовов не имеет значения), то это считается уточнением запроса, и результат будет равносилен однократному вызову с этим аргументом. -Если для полученного [`Grid`](#grid) установлен фильтр [`GridPageSelector`](#grid-page-selector) (или несколько), а `identifier` — это [`longId`](#long-id) элемента измерения одного из этих фильтров, то в соответствующем фильтре будет программно установлен этот элемент. +Если для полученного [`Grid`](#grid) установлен фильтр [`GridPageSelector`](#grid-page-selector) (или несколько), а `identifier` — это [`longId`](./readingGrid.md#long-id) элемента измерения одного из этих фильтров, то в соответствующем фильтре будет программно установлен этот элемент. Возвращает `this`. @@ -481,379 +481,6 @@ for (const chunk of range.generator(1000)) {   -### Интерфейс GridRangeChunk -```ts -interface GridRangeChunk { - cells(): Cells; - rows(): Labels; - columns(): Labels; -} -``` -Интерфейс для обработки куска [`GridRange`](#grid-range). - -  - -```js -cells(): Cells; -``` -Возвращает ссылку на набор ячеек [`Cells`](#cells) текущего куска. - -  - -```js -rows(): Labels; -``` -Возвращает интерфейс [`Labels`](#labels), представляющий заголовки строк. - -  - -```js -columns(): Labels; -``` -Возвращает интерфейс [`Labels`](#labels), представляющий заголовки столбцов. - -  - -### Интерфейс Labels -```ts -interface Labels { - start(): number; - count(): number; - all(): LabelsGroup[]; - get(index: number): LabelsGroup | null; - chunkInstance(): GridRangeChunk; - findLabelByLongId(longId: number): Label | null; -} -``` -Интерфейс, представляющий набор объектов [`LabelsGroup`](#labels-group), то есть набор заголовков строк/столбцов с их возможно многоуровневой структурой. Как правило, его можно получить функциями интерфейса [`GridRangeChunk`](#grid-range-chunk). - -  - -```js -start(): number; -``` -Возвращает номер первой строки/столбца текущего [`GridRangeChunk`](#grid-range-chunk) в таблице [`Grid`](#grid). - -  - -```js -count(): number; -``` -Возвращает количество строк/столбцов в наборе. - -Если `this` относится к строкам, то это значение, которое было посчитано в функции [`GridRange`](#grid-range).[`generator(size)`](#generator) на основе аргумента `size`. - -Если `this` относится к столбцам, то это в точности значение аргумента `columnCount` функции [`Grid`](#grid).[`range(rowStart, rowCount, columnStart, columnCount)`](#range). - -  - -```js -all(): LabelsGroup[]; -``` -Возвращает массив объектов заголовков каждой строки/столбца [`LabelsGroup`](#labels-group). - -  - -```js -get(index: number): LabelsGroup | null; -``` -Аналог `all()[index]`. В случае некорректного индекса возвращает `null`. - -  - -```js -chunkInstance(): GridRangeChunk; -``` -Возвращает обратную ссылку на [`GridRangeChunk`](#grid-range-chunk), из которого был получен `this`. - -  - -```js -findLabelByLongId(longId: number): Label | null; -``` -Возвращает объект [`Label`](#label) по его [`longId`](#long-id), если он присутствует в `this`, иначе — `null`. - -  - -## Интерфейс LabelsGroup -```ts -interface LabelsGroup { - all(): Label[]; - first(): Label; - cells(): Cells; -} -``` -Интерфейс, представляющий многоуровневый набор заголовков конкретной строки или столбца. - -  - -```js -all(): Label[]; -``` -Возвращает массив конкретных заголовков [`Label`](#label). - -  - -```js -first(): Label; -``` -Аналог `all()[0]`. - -  - -```js -cells(): Cells; -``` -Возвращает интерфейс [`Cells`](#cells), предоставляющий доступ к ячейкам данной строки или столбца. - -В случае плоской таблицы [`возвращает`](../appendix/constraints.md#flat-table) `null`. - -  - -### Интерфейс EntityInfo (Label) -```ts -interface Label { - longId(): number; - name(): string; - code(): string | null; - alias(): string; - label(): string; - parentLongId(): number; - hierarchyLongId(): number; -} - -interface EntityInfo = Label; -``` -Интерфейс сущности. Как правило, представляет собой один из заголовков строки или столбца. - -  - - -```js -longId(): number; -``` -Возвращает внутренний идентификатор сущности в системе, уникальный в пределах модели. - -  - - -```js -name(): string; -``` -Возвращает имя сущности. - -  - -```js -code(): string; -``` -Возвращает код сущности. В Optimacros всего две сущности могут иметь код: элементы справочников и кубы. - -  - - -```js -alias(): string; -``` -Возвращает отображаемое имя. - -Если `this` является сущностью элемента справочника, в настройках которого задано некоторое свойство в качестве отображаемого имени (опция `Отображение`), и для этой сущности задано значение этого свойства, то возвращает значение этого свойства. - -Иначе возвращает [`name()`](#label.name). - -  - -```js -label(): string; -``` -То же, что и [`alias()`](#alias). - -  - -```js -parentLongId(): number; -``` -Если сущность является элементом, у которого есть родительский элемент, то возвращает [`longId`](#long-id) сущности родителя. - -Если родительской сущности нет, возвращает `-1`. - -  - -```js -hierarchyLongId(): number; -``` -Если сущность является элементом или сабсетом справочника (включая справочники времени и версий), возвращает [`longId`](#long-id) самого справочника. Если родительского справочника нет, возвращает `-1`. На данный момент этот метод может некорректно работать в зависимости от способа получения `EntityInfo`, для корректной работы рекомендуется получать сущность с помощью интерфейса [`EntitiesInfo`](./common.md#entities-info). - -  - -### Интерфейс Cells -```ts -interface Cells { - all(): Cell[]; - first(): Cell | null; - setValue(value: number | string | boolean | null): this; - count(): number; - chunkInstance(): GridRangeChunk; - getByIndexes(indexes: number[]): Cells; -} -``` -Интерфейс, представляющий (как правило, прямоугольный) набор клеток таблицы. - -  - -```js -all(): Cell[]; -``` -Возвращает одномерный массив всех клеток [`Cell`](#cell). - -  - -```js -first(): Cell | null; -``` -Аналог `all()[0]`. Возвращает `null`, если массив клеток пустой. - -  - - -```js -setValue(value: number | string | boolean | null): this; -``` -Устанавливает одно и то же значение для всех клеток. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых от них клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. Возвращает `this`. - -  - -```js -count(): number; -``` -Возвращает количество клеток в наборе. - -  - - -```js -chunkInstance(): GridRangeChunk; -``` -Возвращает обратную ссылку на [`GridRangeChunk`](#grid-range-chunk), из которого был получен `this`. - -  - -```js -getByIndexes(indexes: number[]): Cells; -``` -Производит выборку из одномерного представления клеток объекта `this` по индексам `indexes` и возвращает новый объект [`Cells`](#cells). В этом случае функция [`chunkInstance()`](#chunk-instance) для нового объекта будет возвращать ссылку на тот же самый объект [`GridRangeChunk`](#grid-range-chunk), что и для `this`. Это *единственный* способ создать непрямоугольный объект [`Cells`](#cells). - -  - -### Интерфейс Cell -```ts -interface Cell { - setValue(value: number | string | boolean | null): this; - - getValue(): number | string | null; - getVisualValue(): string | null; - getNativeValue(): number | string | null; - getContextValue(): string | null; - - definitions(): number[]; - columns(): LabelsGroup | null; - rows(): LabelsGroup | null; - - dropDown(): Labels; - getFormatType(): string; - isEditable(): boolean; -} -``` -Интерфейс, представляющий клетку таблицы. - -  - - -```js -setValue(value: number | string | boolean | null): this; -``` -Устанавливает значение клетки. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. Возвращает `this`. - -  - - -```js -getValue(): number | string | null; -``` -Возвращает значение клетки, которое видит пользователь. Если клетка имеет логический формат, то возвращается строковое значение `'true'` или `'false'`. - -  - -```js -getVisualValue(): string | null; -``` -Возвращает отображаемое значение в ячейке, если куб в формате даты или справочника, для других форматов куба возвращает `null`. - -  - - -```js -getNativeValue(): number | string | null; -``` -Возвращает самородное значение клетки, зависящее от формата. Если клетка имеет формат справочника, то возвращается [`longId`](#long-id). - -В противном случае возвращает то же, что и [`getValue()`](#cell.get-value). - -  - - -```js -getContextValue(): string | null; -``` -Если ячейка имеет формат справочника, в настройках которого задано некоторое свойство `prop` в качестве отображаемого имени (опция `Отображение`), и для этой ячейки задано значение этого свойства, то возвращает строку, состоящую из имени, двойной вертикальной черты и значения свойства `prop`, например, `'#5||Берлин'`. - -В противном случае возвращает `null`. - -  - -```js -definitions(): number[]; -``` -То же, что и [`CubeCell.definitions()`](./cubeCell.md#cube-cell.definitions). - -  - -```js -columns(): LabelsGroup | null; -``` -Возвращает многоуровневый набор заголовков [`LabelsGroup`](#labels-group) конкретного столбца, или `null`, если у клетки нет измерений на столбцах. - -  - -```js -rows(): LabelsGroup | null; -``` -Возвращает многоуровневый набор заголовков [`LabelsGroup`](#labels-group) конкретной строки, или `null`, если у клетки нет измерений на строках. - -  - -```js -dropDown(): Labels; -``` -Возвращает набор заголовков строк [`Labels`](#labels) выпадающего списка, который в интерфейсе пользователя Optimacros можно получить кликом по треугольнику внутри ячейки. Эта функция считается неэффективной, так как выгружает справочник целиком. Лучше зайти в нужный справочник и итерироваться по нему. - -  - -```js -getFormatType(): string; -``` -Возвращает строку с форматом клетки. Возможные значения: `'NUMBER'`, `'BOOLEAN'`, -`'ENTITY'`, `'TIME_ENTITY'`, `'LINE_ITEM_SUBSET'`, `'VERSION'`, `'TEXT'`, `'DATE'`, `'NONE'`. - -  - -```js -isEditable(): boolean; -``` -Возвращает признак возможности редактирования ячейки пользователем. - -  - [API Reference](API.md) [Оглавление](../README.md) From cf4db7ad3163317563bb9c57e0b0724df1df08e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BE=D0=B2?= Date: Mon, 28 Apr 2025 15:55:48 +0300 Subject: [PATCH 09/37] refactoring --- API/audit.md | 4 +- API/common.md | 93 +++++++++- API/cubeCell.md | 8 +- API/dimensions.md | 2 +- API/elementsManipulator.md | 12 +- API/readingGrid.md | 308 +++++++++++++++++++++++++++++++++ API/scriptChains.md | 10 +- API/variables.md | 2 +- API/views.md | 18 +- appendix/constraints.md | 40 +---- cookBook/rowsColumnsFilters.md | 2 +- 11 files changed, 427 insertions(+), 72 deletions(-) create mode 100644 API/readingGrid.md diff --git a/API/audit.md b/API/audit.md index 973a142..b07489e 100644 --- a/API/audit.md +++ b/API/audit.md @@ -93,9 +93,9 @@ eventTypeFilter(data: string | number | (string | number)[]): this; - `string` — название типа события, -- `number` — [`longId`](./views.md#long-id) типа события, +- `number` — [`longId`](./common.md#long-id) типа события, -- `(string | number)[]` — массив (возможно, смешанный) названий и [`longId`](./views.md#long-id) типов событий. +- `(string | number)[]` — массив (возможно, смешанный) названий и [`longId`](./common.md#long-id) типов событий. Возвращает `this`. diff --git a/API/common.md b/API/common.md index 9e1b410..64d4273 100644 --- a/API/common.md +++ b/API/common.md @@ -400,7 +400,7 @@ recalculate(): boolean; ```js backup(path?: string): EntityInfo | boolean; ``` -Сохраняет резервную копию в логах модели: в интерфейсе Optimacros на вкладке `Центр безопастности`->`Логи`->`Резервные копии`. Если указан путь `path`, после создания копии вызовется функция `export()` и вернётся её результат типа `boolean`. Если `path` не указан, возвращает сущность резервной копии в виде [`EntityInfo`](./views.md#entity-info). +Сохраняет резервную копию в логах модели: в интерфейсе Optimacros на вкладке `Центр безопастности`->`Логи`->`Резервные копии`. Если указан путь `path`, после создания копии вызовется функция `export()` и вернётся её результат типа `boolean`. Если `path` не указан, возвращает сущность резервной копии в виде [`EntityInfo`](./common.md#entity-info).   @@ -587,7 +587,7 @@ interface UserInfo { ```js getEntity(): EntityInfo; ``` -Возвращает сущность пользователя в виде [`EntityInfo`](./views.md#entity-info). +Возвращает сущность пользователя в виде [`EntityInfo`](./common.md#entity-info).   @@ -615,7 +615,7 @@ getLastName(): string; ```js getRole(): EntityInfo; ``` -Возвращает сущность роли пользователя в виде [`EntityInfo`](./views.md#entity-info). +Возвращает сущность роли пользователя в виде [`EntityInfo`](./common.md#entity-info).   @@ -653,6 +653,81 @@ setProperty(name: string, value: any): this;   +### Интерфейс EntityInfo (Label) +```ts +interface Label { + longId(): number; + name(): string; + code(): string | null; + alias(): string; + label(): string; + parentLongId(): number; + hierarchyLongId(): number; +} + +``` +Интерфейс сущности. Как правило, представляет собой один из заголовков строки или столбца. + +  + + +```js +longId(): number; +``` +Возвращает внутренний идентификатор сущности в системе, уникальный в пределах модели. + +  + + +```js +name(): string; +``` +Возвращает имя сущности. + +  + +```js +code(): string; +``` +Возвращает код сущности. В Optimacros всего две сущности могут иметь код: элементы справочников и кубы. + +  + + +```js +alias(): string; +``` +Возвращает отображаемое имя. + +Если `this` является сущностью элемента справочника, в настройках которого задано некоторое свойство в качестве отображаемого имени (опция `Отображение`), и для этой сущности задано значение этого свойства, то возвращает значение этого свойства. + +Иначе возвращает [`name()`](#label.name). + +  + +```js +label(): string; +``` +То же, что и [`alias()`](#alias). + +  + +```js +parentLongId(): number; +``` +Если сущность является элементом, у которого есть родительский элемент, то возвращает [`longId`](#long-id) сущности родителя. + +Если родительской сущности нет, возвращает `-1`. + +  + +```js +hierarchyLongId(): number; +``` +Если сущность является элементом или сабсетом справочника (включая справочники времени и версий), возвращает [`longId`](#long-id) самого справочника. Если родительского справочника нет, возвращает `-1`. На данный момент этот метод может некорректно работать в зависимости от способа получения `EntityInfo`, для корректной работы рекомендуется получать сущность с помощью интерфейса [`EntitiesInfo`](#entities-info). + +  + ### Интерфейс EntitiesInfo ```ts interface EntitiesInfo { @@ -660,21 +735,21 @@ interface EntitiesInfo { getCollection(longId: number[]): EntityInfo[]; } ``` -Интерфейс для получения сущности по [`longId`](./views.md#long-id). +Интерфейс для получения сущности по [`longId`](./common.md#long-id).   ```js get(longId: number): EntityInfo | null; ``` -Возвращает сущность [`EntityInfo`](./views.md#entity-info) по её [`longId`](./views.md#long-id). +Возвращает сущность [`EntityInfo`](#entity-info) по её [`longId`](#long-id).   ```js getCollection(longId: number[]): EntityInfo[]; ``` -Возвращает массив сущностей [`EntityInfo`](./views.md#entity-info) по массиву их [`longId`](./views.md#long-id). Корректно работает, только если все переданные `longId` корректные (существуют в модели). Иначе возвращает массив меньшей размерности. Использовать с осторожностью. Порядок возвращаемых сущностей `EntityInfo` может отличаться от порядка переданных `longId`. +Возвращает массив сущностей [`EntityInfo`](#entity-info) по массиву их [`longId`](#long-id). Корректно работает, только если все переданные `longId` корректные (существуют в модели). Иначе возвращает массив меньшей размерности. Использовать с осторожностью. Порядок возвращаемых сущностей `EntityInfo` может отличаться от порядка переданных `longId`.   @@ -703,14 +778,14 @@ interface CopyData { ```js setSourceLongId(longId: number): this; ``` -Устанавливает [`longId`](./views.md#long-id) элемента-источника *заданного измерения*. +Устанавливает [`longId`](#long-id) элемента-источника *заданного измерения*.   ```js setDestLongId(longId: number): this; ``` -Устанавливает [`longId`](./views.md#long-id) элемента-приёмника *заданного измерения*. +Устанавливает [`longId`](#long-id) элемента-приёмника *заданного измерения*.   @@ -731,7 +806,7 @@ enableCustomProperties(): this; ```js setMulticubeLongIds(longIds: number[]): this; ``` -Предписывает произвести копирование в указанных по [`longId`](./views.md#long-id) мультикубах, которые содержат *заданное измерение*. +Предписывает произвести копирование в указанных по [`longId`](#long-id) мультикубах, которые содержат *заданное измерение*.   diff --git a/API/cubeCell.md b/API/cubeCell.md index c2d30fa..83f9b38 100644 --- a/API/cubeCell.md +++ b/API/cubeCell.md @@ -37,7 +37,7 @@ getDimensionIds(): number[]; ```js getDimensionItems(): EntityInfo[]; ``` -Возвращает массив [`EntityInfo`](./views.md#entity-info) элементов измерений куба, которыми определена клетка. Порядок измерений фиксирован и соответствует порядку, в котором их же возвращает функция [`CubeInfo.getDimensions()`](#cube-info.get-dimensions). +Возвращает массив [`EntityInfo`](./common.md#entity-info) элементов измерений куба, которыми определена клетка. Порядок измерений фиксирован и соответствует порядку, в котором их же возвращает функция [`CubeInfo.getDimensions()`](#cube-info.get-dimensions).   @@ -56,7 +56,7 @@ interface CubeInfo extends EntityInfo { getDimensions(): EntityInfo[]; } ``` -Интерфейс информации о кубе. Интерфейс наследуется от [`EntityInfo`](./views.md#entity-info). +Интерфейс информации о кубе. Интерфейс наследуется от [`EntityInfo`](./common.md#entity-info).   @@ -78,7 +78,7 @@ getFormatInfo(): CubeFormatInfo; ```js getDimensions(): EntityInfo[]; ``` -Возвращает массив [`EntityInfo`](./views.md#entity-info) измерений куба. +Возвращает массив [`EntityInfo`](./common.md#entity-info) измерений куба.   @@ -96,7 +96,7 @@ interface CubeFormatInfo { ```js getFormatTypeEntity(): EntityInfo; ``` -Возвращает сущность [`EntityInfo`](./views.md#entity-info) формата куба. Возможные форматы: `'Number'`, `'Text'`, `'Boolean'`, `'Date'`, `'Entity'`, `'Time Entity'`, `'Version'`, `'Line Item Subset'`, `'None'`. +Возвращает сущность [`EntityInfo`](./common.md#entity-info) формата куба. Возможные форматы: `'Number'`, `'Text'`, `'Boolean'`, `'Date'`, `'Entity'`, `'Time Entity'`, `'Version'`, `'Line Item Subset'`, `'None'`.   diff --git a/API/dimensions.md b/API/dimensions.md index 7887860..1e26213 100644 --- a/API/dimensions.md +++ b/API/dimensions.md @@ -348,7 +348,7 @@ interface TimeOptionsTab extends Tab { applyForm(): Object; } ``` -Вкладка `Время`. Интерфейс наследуется от [`Tab`](./views.md#tab). Является [`плоской таблицей`](../appendix/constraints.md#flat-table). Кроме того, является формой, аналогичной форме HTML: после изменения значений ячейки/ячеек требуется ещё вызвать функцию `applyForm()` для применения новых данных к модели. +Вкладка `Время`. Интерфейс наследуется от [`Tab`](./views.md#tab). Является [`плоской таблицей`](../appendix/constraints.md#labelless-table). Кроме того, является формой, аналогичной форме HTML: после изменения значений ячейки/ячеек требуется ещё вызвать функцию `applyForm()` для применения новых данных к модели.   diff --git a/API/elementsManipulator.md b/API/elementsManipulator.md index 456a9d5..363855d 100644 --- a/API/elementsManipulator.md +++ b/API/elementsManipulator.md @@ -43,14 +43,14 @@ interface BaseElementsCreator { ```js setPositionAfter(relativeLongId: number): this; ``` -Устанавливает позицию добавления после [`relativeLongId`](./views.md#long-id). Возвращает `this`. +Устанавливает позицию добавления после [`relativeLongId`](./common.md#long-id). Возвращает `this`.   ```js setPositionBefore(relativeLongId: number): this; ``` -Устанавливает позицию добавления до [`relativeLongId`](./views.md#long-id). Возвращает `this`. +Устанавливает позицию добавления до [`relativeLongId`](./common.md#long-id). Возвращает `this`.   @@ -72,14 +72,14 @@ setPositionEnd(): this; ```js setPositionChildOf(parentLongId: number): this; ``` -Устанавливает позицию добавления элемента как дочернего для [`parentLongId`](./views.md#long-id). Возвращает `this`. +Устанавливает позицию добавления элемента как дочернего для [`parentLongId`](./common.md#long-id). Возвращает `this`.   ```js create(): number[]; ``` -Добавляет элементы и возвращает массив их [`longId`](./views.md#long-id). +Добавляет элементы и возвращает массив их [`longId`](./common.md#long-id).   @@ -131,7 +131,7 @@ interface ElementsDeleter { ```js appendIdentifier(identifier: number): this; ``` -Добавляет в буфер элемент, чей [`longId`](./views.md#long-id) равен `identifier`. Повторное добавление элемента в очередь **не** приводит к ошибкам. Возращает `this`. +Добавляет в буфер элемент, чей [`longId`](./common.md#long-id) равен `identifier`. Повторное добавление элемента в очередь **не** приводит к ошибкам. Возращает `this`.   @@ -160,7 +160,7 @@ interface ElementsReorder { ```js append(longId: number, relativeLongId?: number, position?: string): this; ``` -Добавляет в очередь данные о [`longId`](./views.md#long-id) элемента, который впоследствии будет позиционирован относительно элемента `relativeLongId` (значение по умолчанию: `-1`). Возвращает `this`. Способ позиционирования задаёт аргумент `position` (регистр имеет значение): +Добавляет в очередь данные о [`longId`](./common.md#long-id) элемента, который впоследствии будет позиционирован относительно элемента `relativeLongId` (значение по умолчанию: `-1`). Возвращает `this`. Способ позиционирования задаёт аргумент `position` (регистр имеет значение): `'Before'` — непосредственно перед `relativeLongId`; diff --git a/API/readingGrid.md b/API/readingGrid.md new file mode 100644 index 0000000..d6c41a8 --- /dev/null +++ b/API/readingGrid.md @@ -0,0 +1,308 @@ +# Интерфейсы для чтения представления в виде таблицы + +## Интерфейс GridRangeChunk +```ts +interface GridRangeChunk { + cells(): Cells; + rows(): Labels; + columns(): Labels; +} +``` +Интерфейс для обработки части строк [`GridRange`](./views.md#grid-range) — чанка. Содержит информацию о заголовках (возможно многоуровневых) и ячейках двумерной таблицы. + +  + +```js +cells(): Cells; +``` +Возвращает ссылку на набор ячеек [`Cells`](#cells) текущего чанка. + +  + +```js +rows(): Labels; +``` +Возвращает интерфейс [`Labels`](#labels), представляющий заголовки строк. + +  + +```js +columns(): Labels; +``` +Возвращает интерфейс [`Labels`](#labels), представляющий заголовки столбцов. + +  + +### Интерфейс Labels +```ts +interface Labels { + start(): number; + count(): number; + all(): LabelsGroup[]; + get(index: number): LabelsGroup | null; + chunkInstance(): GridRangeChunk; + findLabelByLongId(longId: number): Label | null; +} +``` +Интерфейс, представляющий набор объектов [`LabelsGroup`](#labels-group), то есть набор заголовков строк/столбцов с их возможно многоуровневой структурой. Как правило, его можно получить функциями интерфейса [`GridRangeChunk`](#grid-range-chunk). + +  + +```js +start(): number; +``` +Возвращает номер первой строки/столбца текущего [`GridRangeChunk`](#grid-range-chunk) в таблице [`Grid`](./views.md#grid). + +  + +```js +count(): number; +``` +Возвращает количество строк/столбцов в наборе. + +Если `this` относится к строкам, то это значение, которое было посчитано в функции [`GridRange`](./views.md#grid-range).[`generator(size)`](./views.md#generator) на основе аргумента `size`. + +Если `this` относится к столбцам, то это в точности значение аргумента `columnCount` функции [`Grid`](./views.md#grid).[`range(rowStart, rowCount, columnStart, columnCount)`](./views.md#range). + +  + +```js +all(): LabelsGroup[]; +``` +Возвращает массив объектов заголовков каждой строки/столбца [`LabelsGroup`](#labels-group). + +  + +```js +get(index: number): LabelsGroup | null; +``` +Аналог `all()[index]`. В случае некорректного индекса возвращает `null`. + +  + +```js +chunkInstance(): GridRangeChunk; +``` +Возвращает обратную ссылку на [`GridRangeChunk`](#grid-range-chunk), из которого был получен `this`. + +  + +```js +findLabelByLongId(longId: number): Label | null; +``` +Возвращает объект [`Label`](#label) по его [`longId`](./common.md#long-id), если он присутствует в `this`, иначе — `null`. + +  + +## Интерфейс LabelsGroup +```ts +interface LabelsGroup { + all(): Label[]; + first(): Label; + cells(): Cells; +} +``` +Интерфейс, представляющий многоуровневый набор заголовков конкретной строки или столбца. + +  + +```js +all(): Label[]; +``` +Возвращает массив конкретных заголовков [`Label`](#label). + +  + +```js +first(): Label; +``` +Аналог `all()[0]`. + +  + +```js +cells(): Cells; +``` +Возвращает интерфейс [`Cells`](#cells), предоставляющий доступ к ячейкам данной строки или столбца. + +  + +### Интерфейс Label +```ts +interface Label = EntityInfo; +``` +Интерфейс сущности, полученный при чтении грида (таблицы). Как правило, представляет собой один из заголовков строки или столбца. Должен быть идентичен интерфейсу [`EntitiesInfo`](./common.md#entities-info), но может отличаться. Поэтому рекомендуется получить [`longId`](./common.md#long-id) сущности с помощью этого интерфейса, а затем получить [`EntitiesInfo`](./common.md#entities-info) с помощью метода [`EntitiesInfo.get()`](./common.md#entities-info). + +  + +### Интерфейс Cells +```ts +interface Cells { + all(): Cell[]; + first(): Cell | null; + setValue(value: number | string | boolean | null): this; + count(): number; + chunkInstance(): GridRangeChunk; + getByIndexes(indexes: number[]): Cells; +} +``` +Интерфейс, представляющий (как правило, прямоугольный) набор клеток таблицы. + +  + +```js +all(): Cell[]; +``` +Возвращает одномерный массив всех клеток [`Cell`](#cell). + +  + +```js +first(): Cell | null; +``` +Аналог `all()[0]`. Возвращает `null`, если массив клеток пустой. + +  + + +```js +setValue(value: number | string | boolean | null): this; +``` +Устанавливает одно и то же значение для всех клеток. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых от них клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. Возвращает `this`. + +  + +```js +count(): number; +``` +Возвращает количество клеток в наборе. + +  + + +```js +chunkInstance(): GridRangeChunk; +``` +Возвращает обратную ссылку на [`GridRangeChunk`](#grid-range-chunk), из которого был получен `this`. + +  + +```js +getByIndexes(indexes: number[]): Cells; +``` +Производит выборку из одномерного представления клеток объекта `this` по индексам `indexes` и возвращает новый объект [`Cells`](#cells). В этом случае функция [`chunkInstance()`](#chunk-instance) для нового объекта будет возвращать ссылку на тот же самый объект [`GridRangeChunk`](#grid-range-chunk), что и для `this`. Это *единственный* способ создать непрямоугольный объект [`Cells`](#cells). + +  + +### Интерфейс Cell +```ts +interface Cell { + setValue(value: number | string | boolean | null): this; + + getValue(): number | string | null; + getVisualValue(): string | null; + getNativeValue(): number | string | null; + getContextValue(): string | null; + + definitions(): number[]; + columns(): LabelsGroup | null; + rows(): LabelsGroup | null; + + dropDown(): Labels; + getFormatType(): string; + isEditable(): boolean; +} +``` +Интерфейс, представляющий клетку таблицы. + +  + + +```js +setValue(value: number | string | boolean | null): this; +``` +Устанавливает значение клетки. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. Возвращает `this`. + +  + + +```js +getValue(): number | string | null; +``` +Возвращает значение клетки, которое видит пользователь. Если клетка имеет логический формат, то возвращается строковое значение `'true'` или `'false'`. + +  + +```js +getVisualValue(): string | null; +``` +Возвращает отображаемое значение в ячейке, если куб в формате даты или справочника, для других форматов куба возвращает `null`. + +  + + +```js +getNativeValue(): number | string | null; +``` +Возвращает самородное значение клетки, зависящее от формата. Если клетка имеет формат справочника, то возвращается [`longId`](./common.md#long-id). + +В противном случае возвращает то же, что и [`getValue()`](#cell.get-value). + +  + + +```js +getContextValue(): string | null; +``` +Если ячейка имеет формат справочника, в настройках которого задано некоторое свойство `prop` в качестве отображаемого имени (опция `Отображение`), и для этой ячейки задано значение этого свойства, то возвращает строку, состоящую из имени, двойной вертикальной черты и значения свойства `prop`, например, `'#5||Берлин'`. + +В противном случае возвращает `null`. + +  + +```js +definitions(): number[]; +``` +То же, что и [`CubeCell.definitions()`](./cubeCell.md#cube-cell.definitions). + +  + +```js +columns(): LabelsGroup | null; +``` +Возвращает многоуровневый набор заголовков [`LabelsGroup`](#labels-group) конкретного столбца, или `null`, если у клетки нет измерений на столбцах. + +  + +```js +rows(): LabelsGroup | null; +``` +Возвращает многоуровневый набор заголовков [`LabelsGroup`](#labels-group) конкретной строки, или `null`, если у клетки нет измерений на строках. + +  + +```js +dropDown(): Labels; +``` +Возвращает набор заголовков строк [`Labels`](#labels) выпадающего списка, который в интерфейсе пользователя Optimacros можно получить кликом по треугольнику внутри ячейки. Эта функция считается неэффективной, так как выгружает справочник целиком. Лучше зайти в нужный справочник и итерироваться по нему. + +  + +```js +getFormatType(): string; +``` +Возвращает строку с форматом клетки. Возможные значения: `'NUMBER'`, `'BOOLEAN'`, +`'ENTITY'`, `'TIME_ENTITY'`, `'LINE_ITEM_SUBSET'`, `'VERSION'`, `'TEXT'`, `'DATE'`, `'NONE'`. + +  + +```js +isEditable(): boolean; +``` +Возвращает признак возможности редактирования ячейки пользователем. + +  + +[API Reference](API.md) + +[Оглавление](../README.md) diff --git a/API/scriptChains.md b/API/scriptChains.md index 835d42f..0f13a59 100644 --- a/API/scriptChains.md +++ b/API/scriptChains.md @@ -18,7 +18,7 @@ interface ResultActionsInfo { ```js makeMacrosAction(identifier: string | number): ResultMacrosAction; ``` -Создаёт и возвращает действие [`ResultMacrosAction`](#result-macros-action) запуска существующего в модели скрипта. Аргумент `identifier` означает имя или [`longId`](./views.md#long-id) скрипта. +Создаёт и возвращает действие [`ResultMacrosAction`](#result-macros-action) запуска существующего в модели скрипта. Аргумент `identifier` означает имя или [`longId`](./common.md#long-id) скрипта.   @@ -32,28 +32,28 @@ makeCodeExecutionAction(code: string): CodeExecutionAction; ```js makeDashboardOpenAction(identifier: string | number): ResultOpenAction; ``` -Создаёт и возвращает действие [`ResultOpenAction`](#result-open-action) открытия существующего в модели дашборда. Аргумент `identifier` означает имя или [`longId`](./views.md#long-id) дашборда. +Создаёт и возвращает действие [`ResultOpenAction`](#result-open-action) открытия существующего в модели дашборда. Аргумент `identifier` означает имя или [`longId`](./common.md#long-id) дашборда.   ```js makeContextTableOpenAction(identifier: string | number): ResultOpenAction; ``` -Создаёт и возвращает действие [`ResultOpenAction`](#result-open-action) открытия существующей в модели контекстной таблицы. Аргумент `identifier` означает имя или [`longId`](./views.md#long-id) контекстной таблицы. +Создаёт и возвращает действие [`ResultOpenAction`](#result-open-action) открытия существующей в модели контекстной таблицы. Аргумент `identifier` означает имя или [`longId`](./common.md#long-id) контекстной таблицы.   ```js makeMulticubeViewOpenAction(multicube: string | number, view?: string | number | null): ResultOpenAction; ``` -Создаёт и возвращает действие [`ResultOpenAction`](#result-open-action) открытия существующего в модели мультикуба. Аргумент `identifier` означает имя или [`longId`](./views.md#long-id) мультикуба, `view` означает имя или [`longId`](./views.md#long-id) представления. +Создаёт и возвращает действие [`ResultOpenAction`](#result-open-action) открытия существующего в модели мультикуба. Аргумент `identifier` означает имя или [`longId`](./common.md#long-id) мультикуба, `view` означает имя или [`longId`](./common.md#long-id) представления.   ```js makeListViewOpenAction(list: string | number, view?: string | number | null): ResultOpenAction; ``` -Создаёт и возвращает действие [`ResultOpenAction`](#result-open-action) открытия существующего в модели справочника. Аргумент `identifier` означает имя или [`longId`](./views.md#long-id) справочника, `view` означает имя или [`longId`](./views.md#long-id) представления. +Создаёт и возвращает действие [`ResultOpenAction`](#result-open-action) открытия существующего в модели справочника. Аргумент `identifier` означает имя или [`longId`](./common.md#long-id) справочника, `view` означает имя или [`longId`](./common.md#long-id) представления.   diff --git a/API/variables.md b/API/variables.md index 29a644c..f6c3a0f 100644 --- a/API/variables.md +++ b/API/variables.md @@ -31,7 +31,7 @@ interface Variable { ```js isEntity(): boolean; ``` -Возвращает `true`, если значение переменной - объект [`EntityInfo`](./views.md#entity-info), и `false` в противном случае. +Возвращает `true`, если значение переменной - объект [`EntityInfo`](./common.md#entity-info), и `false` в противном случае.   diff --git a/API/views.md b/API/views.md index 12249b7..b9c4ea1 100644 --- a/API/views.md +++ b/API/views.md @@ -113,7 +113,7 @@ cleanCellsData(cubesIdentifiers?: number[]): this; ```js cubeCellSelector(identifier: string | number): CubeCellSelectorBuilder; ``` -Возвращает интерфейс [`CubeCellSelectorBuilder`](./cubeCell.md#cube-cell-selector-builder) выборки клеток для куба `identifier`. `identifier` должен быть именем или [`longId`](./readingGrid.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение. +Возвращает интерфейс [`CubeCellSelectorBuilder`](./cubeCell.md#cube-cell-selector-builder) выборки клеток для куба `identifier`. `identifier` должен быть именем или [`longId`](./common.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение.   @@ -121,14 +121,14 @@ cubeCellSelector(identifier: string | number): CubeCellSelectorBuilder; cubeCellUpdater(identifier: string | number): CubeCellUpdaterBuilder; ``` -Возвращает интерфейс [`CubeCellUpdaterBuilder`](./cubeCell.md#cube-cell-updater-builder) обновления клеток куба с именем или идентификатором `identifier` по формуле. `identifier` должен быть именем или [`longId`](./readingGrid.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение. +Возвращает интерфейс [`CubeCellUpdaterBuilder`](./cubeCell.md#cube-cell-updater-builder) обновления клеток куба с именем или идентификатором `identifier` по формуле. `identifier` должен быть именем или [`longId`](./common.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение.   ```js getCubeInfo(identifier: string | number): CubeInfo; ``` -Возвращает интерфейс [`CubeInfo`](./cubeCell.md#cube-info) для получения информации о кубе `identifier`. `identifier` должен быть именем или [`longId`](./readingGrid.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение. +Возвращает интерфейс [`CubeInfo`](./cubeCell.md#cube-info) для получения информации о кубе `identifier`. `identifier` должен быть именем или [`longId`](./common.md#long-id) куба. При указании некорректного `identifier` выбрасывается исключение.   @@ -217,9 +217,9 @@ rowsFilter(data: string | string[] | number | number[]): this; `string[]` — массив названий строк; -`number` — [`longId`](./readingGrid.md#long-id) строки; +`number` — [`longId`](./common.md#long-id) строки; -`number[]` — массив [`longId`](./readingGrid.md#long-id) строк. +`number[]` — массив [`longId`](./common.md#long-id) строк. Возвращает `this`. @@ -245,11 +245,11 @@ withoutValues(): this; ```js addDependentContext(identifier: number): this; ``` -Добавляет в фильтр по строкам весь зависимый контекст переданного [`longId`](./readingGrid.md#long-id) `identifier`: материнские и дочерние элементы всех уровней. +Добавляет в фильтр по строкам весь зависимый контекст переданного [`longId`](./common.md#long-id) `identifier`: материнские и дочерние элементы всех уровней. Если эта функция многократно вызывается с аргументами, один из которых является потомком остальных (порядок вызовов не имеет значения), то это считается уточнением запроса, и результат будет равносилен однократному вызову с этим аргументом. -Если для полученного [`Grid`](#grid) установлен фильтр [`GridPageSelector`](#grid-page-selector) (или несколько), а `identifier` — это [`longId`](./readingGrid.md#long-id) элемента измерения одного из этих фильтров, то в соответствующем фильтре будет программно установлен этот элемент. +Если для полученного [`Grid`](#grid) установлен фильтр [`GridPageSelector`](#grid-page-selector) (или несколько), а `identifier` — это [`longId`](./common.md#long-id) элемента измерения одного из этих фильтров, то в соответствующем фильтре будет программно установлен этот элемент. Возвращает `this`. @@ -455,9 +455,9 @@ cellCount(): number; ```js generator(size?: number): IterableIterator; ``` -Возвращает генератор, при каждом обращении возвращающий интерфейс [`GridRangeChunk`](#grid-range-chunk) размером *не более* `size` ячеек, позволяющий обрабатывать `GridRange` покусочно. +Возвращает генератор, при каждом обращении возвращающий интерфейс [`GridRangeChunk`](./readingGrid.md#grid-range-chunk) размером *не более* `size` ячеек, позволяющий обрабатывать `GridRange` покусочно. -Каждый возвращаемый [`GridRangeChunk`](#grid-range-chunk) содержит целое количество строк, т. е. все колонки `GridRange`, а количество строк в нём определяется по формуле `size / columnCount()`. Здесь используется целочисленное деление с округлением в большую сторону. Например, если в таблице `14` столбцов, а параметр `size` равен `1500`, то генератор вернёт [`GridRangeChunk`](#grid-range-chunk) из `1500 / 14 = 107.14 ≈ 108` строк, в котором будет `14 * 108 = 1512` ячеек. +Каждый возвращаемый [`GridRangeChunk`](./readingGrid.md#grid-range-chunk) содержит целое количество строк, т. е. все колонки `GridRange`, а количество строк в нём определяется по формуле `size / columnCount()`. Здесь используется целочисленное деление с округлением в большую сторону. Например, если в таблице `14` столбцов, а параметр `size` равен `1500`, то генератор вернёт [`GridRangeChunk`](./readingGrid.md#grid-range-chunk) из `1500 / 14 = 107.14 ≈ 108` строк, в котором будет `14 * 108 = 1512` ячеек. Значение аргумента `size` ограничено снизу значением `500` и сверху значением `5000`, поэтому в скриптах 1.0 [`невозможно`](../appendix/constraints.md#generator) работать с `GridRange` с б*О*льшим количеством столбцов. Значение по умолчанию: `1500`. diff --git a/appendix/constraints.md b/appendix/constraints.md index 0e66891..c791b0d 100644 --- a/appendix/constraints.md +++ b/appendix/constraints.md @@ -7,18 +7,10 @@   - -## Плоские таблицы + +## Таблицы без строк и/или столбцов -Если в сводной таблице на столбцах нет измерений (в этом случае в интерфейсе отображается один столбец), то к ячейкам невозможно получить доступ через функцию [`LabelsGroup`](../API/views.md#labels-group).`cells()`. В этой ситуации она возвращает `null`: - -```js -for (const chunk of generator) { - const rowsCells = chunk.rows().all()[0].cells(); // null -} -``` - -В этом случае система скриптов создаёт виртуальное измерение, к которому можно получить доступ стандартным способом, и вызов +Если в сводной таблице в строках или столбцах нет измерений, система скриптов создаёт виртуальное измерение, к которому можно получить доступ стандартным способом, и вызов ```js definitionInfo.getColumnDimensions()[0]getDimensionEntity().name(); @@ -26,31 +18,11 @@ definitionInfo.getColumnDimensions()[0]getDimensionEntity().name(); вернёт специальное значение `'Empty 1 0'`. -Характерный пример плоской таблицы – [`вкладка`](../API/dimensions.md#time-options-tab) настроек времени. +Характерный пример такой таблицы – [`вкладка`](../API/dimensions.md#time-options-tab) настроек времени. -Для решения этой проблемы следует использовать функцию [`GridRangeChunk`](../API/views.md#grid-range-chunk).`cells()`, которая возвращает линейный массив, параллельный массиву [`GridRangeChunk`](../API/views.md#grid-range-chunk).`rows()`. Пример кода, который в настройках времени устанавливает нужное значение в ячейку `Current Month`, используя такой подход: - -```js -for (const chunk of generator) { - let currentMonthIndex = null; - - chunk.rows().all().forEach((rowLabels, index) => { - const name = rowLabels.first().name(); - if (name === 'Current Month') { - currentMonthIndex = index; - } - }); - - if (currentMonthIndex === null) { - throw new Error(`Option 'Current Month' not found`); - } - - const cells = chunk.cells().all(); - cells[currentMonthIndex].setValue(newCurrentMonthValue); -} -``` +Такое измерение не содержит заголовков и вызов [`LabelsGroup`](../API/readingGrid.md#labels-group).`all()` вернёт пустой массив. -Если в сводной таблице нет измерений на *строках*, ситуация полностью аналогична описанной. +Если таблица не собержит ни строк, ни колонок, то доступ к единственной ячейке возможет только с помощью метода [`GridRangeChunk`](../API/readingGrid.md#grid-range-chunk).`cells()`.   diff --git a/cookBook/rowsColumnsFilters.md b/cookBook/rowsColumnsFilters.md index fff4a93..1a2d0df 100644 --- a/cookBook/rowsColumnsFilters.md +++ b/cookBook/rowsColumnsFilters.md @@ -228,7 +228,7 @@ console.log(`Filter dimensions: ${pageSelectedNames.join(', ')} \n`); ![Измерения в фильтрах МК условия и расчёты 2, скрипт](./pic/rcf_FiltersReady2.png) -Необходимо заметить, что в выводе измерений столбцов `Column dimensions` появилось измерение `'Empty 1 0'`, хотя в представлении МК измерений на столбцах нет. Подробнее про это можно прочитать [здесь](../appendix/constraints.md#flat-table). +Необходимо заметить, что в выводе измерений столбцов `Column dimensions` появилось измерение `'Empty 1 0'`, хотя в представлении МК измерений на столбцах нет. Подробнее про это можно прочитать [здесь](../appendix/constraints.md#labelless-table). [Курс молодого бойца](cookBook.md) From cddfdae163646b8e05c75557375b5ea0e31f15e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BE=D0=B2?= Date: Mon, 28 Apr 2025 16:32:12 +0300 Subject: [PATCH 10/37] refactoring vol 2 --- API/common.md | 9 +++++---- API/readingGrid.md | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/API/common.md b/API/common.md index 64d4273..82d87b4 100644 --- a/API/common.md +++ b/API/common.md @@ -145,7 +145,7 @@ interface CellBuffer { ```js set(cell: Cell | CubeCell, value: number | string | boolean | null): this; ``` -Устанавливает значение `value` в клетку `cell` в буфере. Возвращает `this`. +Устанавливает значение `value` в клетку `cell` в буфере. В качестве значения `value` можно передать то же, что и для метода [`Cell.setValue()`](./readingGrid.md#cell.set-value). Возвращает `this`.   @@ -615,7 +615,7 @@ getLastName(): string; ```js getRole(): EntityInfo; ``` -Возвращает сущность роли пользователя в виде [`EntityInfo`](./common.md#entity-info). +Возвращает сущность роли пользователя в виде [`EntityInfo`](#entity-info).   @@ -678,7 +678,7 @@ longId(): number;   - + ```js name(): string; ``` @@ -686,6 +686,7 @@ name(): string;   + ```js code(): string; ``` @@ -701,7 +702,7 @@ alias(): string; Если `this` является сущностью элемента справочника, в настройках которого задано некоторое свойство в качестве отображаемого имени (опция `Отображение`), и для этой сущности задано значение этого свойства, то возвращает значение этого свойства. -Иначе возвращает [`name()`](#label.name). +Иначе возвращает [`name()`](#name).   diff --git a/API/readingGrid.md b/API/readingGrid.md index d6c41a8..e16533c 100644 --- a/API/readingGrid.md +++ b/API/readingGrid.md @@ -221,7 +221,7 @@ interface Cell { ```js setValue(value: number | string | boolean | null): this; ``` -Устанавливает значение клетки. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. Возвращает `this`. +Устанавливает значение клетки. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. В случае клетки формата справочника в качестве значени можно использовать [имя элемента](./common.md#name), его [код](./common.md#code), [`longId`](./common.md#long-id) или [пару `отображаемое-имя||имя`](cell.get-context-value). Возвращает `this`.   @@ -254,7 +254,7 @@ getNativeValue(): number | string | null; ```js getContextValue(): string | null; ``` -Если ячейка имеет формат справочника, в настройках которого задано некоторое свойство `prop` в качестве отображаемого имени (опция `Отображение`), и для этой ячейки задано значение этого свойства, то возвращает строку, состоящую из имени, двойной вертикальной черты и значения свойства `prop`, например, `'#5||Берлин'`. +Если ячейка имеет формат справочника, в настройках которого задано некоторое свойство `prop` в качестве отображаемого имени (опция `Отображение`), и для этой ячейки задано значение этого свойства, то возвращает строку, состоящую из имени, двойной вертикальной черты и значения свойства `prop`. Например, для отображамого имени `Берлин` и имени элемента `#5` — `'Берлин||#5'`. В противном случае возвращает `null`. From 9c4fda61ef9a5caa97d03a928f94e131f893c246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BE=D0=B2?= Date: Tue, 29 Apr 2025 09:30:04 +0300 Subject: [PATCH 11/37] dropDownSelector() --- API/readingGrid.md | 85 ++++++++++++++++++++++++++++++++++++++++++++- API/scripts.om.d.ts | 3 +- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/API/readingGrid.md b/API/readingGrid.md index e16533c..15b7019 100644 --- a/API/readingGrid.md +++ b/API/readingGrid.md @@ -209,6 +209,7 @@ interface Cell { rows(): LabelsGroup | null; dropDown(): Labels; + dropDownSelector(): DropDownSelector; getFormatType(): string; isEditable(): boolean; } @@ -284,7 +285,19 @@ rows(): LabelsGroup | null; ```js dropDown(): Labels; ``` -Возвращает набор заголовков строк [`Labels`](#labels) выпадающего списка, который в интерфейсе пользователя Optimacros можно получить кликом по треугольнику внутри ячейки. Эта функция считается неэффективной, так как выгружает справочник целиком. Лучше зайти в нужный справочник и итерироваться по нему. + +Этот метод признан устаревшим. Вместо него стоит использовать метод [`dropDownSelector()`](#cell.dropdown-selector). + +Возвращает набор заголовков строк [`Labels`](#labels) выпадающего списка, который в интерфейсе пользователя `Optimacros` можно получить кликом по треугольнику внутри ячейки. Эта функция считается неэффективной, так как выгружает справочник целиком. Лучше зайти в нужный справочник и итерироваться по нему. + +  + + +```js +dropDownSelector(): DropDownSelector; +``` + +Позволяет постранично читать набор опций выпадающего списка значений клетки. Требует наличия `SHARED` блокировки для всех случаев, кроме колонки `Api Service Model` [таблицы веб-сервисов воркспейса](./apiServicesAdministration.md), которая требует отсутствия блокировок — `UNLOCK` (чтение опций клеток колонки `Api Service Script` требует `SHARED` блокировки, так как список скриптов без чтения модели получить не выйдет). Возвращает ссылку на интерфейс [`DropDownSelector`](#dropdown-selector) выпадающего списка, который в интерфейсе пользователя `Optimacros` можно получить кликом по треугольнику внутри ячейки.   @@ -296,6 +309,7 @@ getFormatType(): string;   + ```js isEditable(): boolean; ``` @@ -303,6 +317,75 @@ isEditable(): boolean;   +### Интерфейс DropDownSelector + +```js +interface DropDownSelector { + totalCount(): number; + generator(chunkSize: number | null): IterableIterator; +} +``` + +Интерфейс постраничного получения опций выпадающего списка для клеток формата сущности — `'ENTITY'`, `'TIME_ENTITY'`, `'VERSION'`. Который должен во всех случаях совпадать со списком, доступным пользователю через `web`-интерфейс (со всеми применимыми фильтрациями). + +Для клеток, доступных только для чтения, список опций всё равно доступен, хотя изменение значения клетки невозможно, поэтому чтобы понять, можно ли изменять клетку, стоит обратиться к методу [`Cell.isEditable()`](#cell.is-editable). Если недоступно даже чтение значения клетки, попытка получения данного интерфейса приведёт к ошибке. + +По неизвестной науке причине с помощью этого интерфейса также **возможно** чтение списка доспупных пользовательских измерений мультикуба `User Lists`. По той же причине, если в справочнике типа `Cube Link` не установлено значение мультикуба `Multicube Link`, то попытка чтения опций кубов `Cube Link` приведёт к ошибке. Эта же причниа влияет и на то, что если в справочнике создать свойство с форматом этого же или родительского справочника и применить зависимый контекст по измерению, то в `web`-интерфейсе фильтрация **не** будет работать, но интерфейс `DropDownSelector` **будет** работать с фильтрацией. + +Для получения новыйх страниц требуется блокировка того же уровня, что и для получения ссылки на сам интерфейс с помощью [`Cell.dropDownSelector`](#cell.dropdown-selector). + +Также стоит отметить, что наличе опции в выпадающем списке не гарантирует, что данное значение может быть установлено. + +  + +```js +totalCount(): number; +``` +Возвращает общее количество опций выпадающего списка. + +  + +```js +generator(chunkSize: number | null): IterableIterator; +``` +Метод получения итератора для постраничного чтения опций выпадающего списка. Аргумент `chunkSize` — максимальное количество опций на одной странице итератора в интервале от 500 до 5000 (по умолчанию 1000). Влияние параметра `chunkSize` на скорость работы итератора достаточно не изучено и это предстоит устанавливать в каждом конкретном случае. Возвращает итерируемый объект для чтения страниц опций выдающего списка [`DropDownSelectorChunk`](#dropdown-selector-chunk). + +  + +### Интерфейс DropDownSelectorChunk + +```js +interface DropDownSelectorChunk { + start(): number; + count(): number; + all(): Label[]; +} +``` +Интерфейс содержащий одну страницу опций выпадающего списка возможных значений клетки. + +  + +```js +start(): number; +``` +Возвращает номер первой опции текущей страницы выдающего списка, начиная отсчёт с 0. + +  + +```js +count(): number; +``` +Возвращает общее число опций на данной странице. + +  + +```js +all(): Label[]; +``` +Возвращает список сущностей [Label](#label) опций выпадающего списка. + +  + [API Reference](API.md) [Оглавление](../README.md) diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 0086aff..498db23 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -22,10 +22,11 @@ export interface Cell { columns(): LabelsGroup | null; rows(): LabelsGroup | null; + /** DEPRECATED */ dropDown(): Labels; + dropDownSelector(): DropDownSelector; getFormatType(): string; isEditable(): boolean; - dropDownSelector(): DropDownSelector; } export interface DropDownSelector { From 4e4b8504185a7b5717c1e180eda2c23d8def8074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BE=D0=B2?= Date: Tue, 29 Apr 2025 09:40:07 +0300 Subject: [PATCH 12/37] dropDownSelector() vol 2 --- API/readingGrid.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/API/readingGrid.md b/API/readingGrid.md index 15b7019..18290a8 100644 --- a/API/readingGrid.md +++ b/API/readingGrid.md @@ -296,8 +296,7 @@ dropDown(): Labels; ```js dropDownSelector(): DropDownSelector; ``` - -Позволяет постранично читать набор опций выпадающего списка значений клетки. Требует наличия `SHARED` блокировки для всех случаев, кроме колонки `Api Service Model` [таблицы веб-сервисов воркспейса](./apiServicesAdministration.md), которая требует отсутствия блокировок — `UNLOCK` (чтение опций клеток колонки `Api Service Script` требует `SHARED` блокировки, так как список скриптов без чтения модели получить не выйдет). Возвращает ссылку на интерфейс [`DropDownSelector`](#dropdown-selector) выпадающего списка, который в интерфейсе пользователя `Optimacros` можно получить кликом по треугольнику внутри ячейки. +Позволяет постранично читать набор опций выпадающего списка значений клетки. Требует наличия `SHARED` блокировки для всех случаев, кроме колонки `Api Service Model` [таблицы веб-сервисов воркспейса](./apiServicesAdministration.md), которая требует отсутствия блокировок — `UNLOCK` (чтение опций клеток колонки `Api Service Script` требует `SHARED` блокировки, так как список скриптов без чтения модели получить не выйдет). Вызов на клетке, не содержащей выпадающего списка, приведёт к ошибке. Возвращает ссылку на интерфейс [`DropDownSelector`](#dropdown-selector) выпадающего списка, который в интерфейсе пользователя `Optimacros` можно получить кликом по треугольнику внутри ячейки.   @@ -330,7 +329,7 @@ interface DropDownSelector { Для клеток, доступных только для чтения, список опций всё равно доступен, хотя изменение значения клетки невозможно, поэтому чтобы понять, можно ли изменять клетку, стоит обратиться к методу [`Cell.isEditable()`](#cell.is-editable). Если недоступно даже чтение значения клетки, попытка получения данного интерфейса приведёт к ошибке. -По неизвестной науке причине с помощью этого интерфейса также **возможно** чтение списка доспупных пользовательских измерений мультикуба `User Lists`. По той же причине, если в справочнике типа `Cube Link` не установлено значение мультикуба `Multicube Link`, то попытка чтения опций кубов `Cube Link` приведёт к ошибке. Эта же причниа влияет и на то, что если в справочнике создать свойство с форматом этого же или родительского справочника и применить зависимый контекст по измерению, то в `web`-интерфейсе фильтрация **не** будет работать, но интерфейс `DropDownSelector` **будет** работать с фильтрацией. +По неизвестной науке причине с помощью этого интерфейса также **возможно** чтение списка доспупных пользовательских измерений мультикуба `User Lists`. По той же причине, если в справочнике типа `Cube Link` не установлено значение мультикуба `Multicube Link`, то у клетки пропадает выпадающий список полностью и она становится нередактируемой, а значит, попытка чтения опций кубов `Cube Link` приведёт к ошибке. Эта же причниа влияет и на то, что если в справочнике создать свойство с форматом этого же или родительского справочника и применить зависимый контекст по измерению, то в `web`-интерфейсе фильтрация **не** будет работать, но интерфейс `DropDownSelector` **будет** работать с фильтрацией. Для получения новыйх страниц требуется блокировка того же уровня, что и для получения ссылки на сам интерфейс с помощью [`Cell.dropDownSelector`](#cell.dropdown-selector). From 14bb94addde8267e9c8f3820bbd66dc4ea111545 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Tue, 29 Apr 2025 14:30:32 +0700 Subject: [PATCH 13/37] - API Secrets is added to the main API page - MD file for secrets is renamed --- API/API.md | 11 ++++++++++- API/{apiSecrets.md => secrets.md} | 0 2 files changed, 10 insertions(+), 1 deletion(-) rename API/{apiSecrets.md => secrets.md} (100%) diff --git a/API/API.md b/API/API.md index a1d8011..5c4524f 100644 --- a/API/API.md +++ b/API/API.md @@ -5,7 +5,7 @@ * выполнение скрипта: информация об окружении и управление выводом/действиями после завершения работы (см. `Common`, `Environment`, `Optimization`); * взаимодействие с моделью, как сущностью: создание бэкапа, пересчёт всей модели (см. `Common.ModelInfo`, `Users`); * взаимодействие с данными и метаданными модели (см. `Multicubes`, `Times`, `Versions`, `Lists`, `Common.CopyData`); -* взаимодействие с внешним миром (см. `Common.ApiService`, `Filesystems`, `Connectors`, `Notifications`); +* взаимодействие с внешним миром (см. `Common.ApiService`, `Filesystems`, `Connectors`, `Notifications`, `Secrets`); * административное: настройка API-сервисов (`ApiServices`), работа с аудитом (`Audit`); * функции, напрямую не связанные с Оптимакросом, функции помощники — интерфейс `Crypto`. @@ -33,6 +33,7 @@ 1. [Аудит](audit.md) 1. [Криптография, хэширование и вспомогательные функции](crypto.md) 1. [Пользователи](users.md) +1. [Секреты](secrets.md) ## Интерфейс OM ```ts @@ -52,6 +53,7 @@ interface OM { readonly audit: Audit; readonly crypto: Crypto; readonly users: Users; + readonly secrets: Secrets; } var om: OM; @@ -165,4 +167,11 @@ readonly users: Users;   +```js +readonly secrets: Secrets; +``` +Ссылка на интерфейс [`Secrets`](./secrets.md). + +  + [Оглавление](../README.md) diff --git a/API/apiSecrets.md b/API/secrets.md similarity index 100% rename from API/apiSecrets.md rename to API/secrets.md From d462b738aa205fde88bd567674f29777d2f23989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BE=D0=B2?= Date: Tue, 29 Apr 2025 11:38:50 +0300 Subject: [PATCH 14/37] script info methods vol 1 --- API/common.md | 26 +++++++++++++++++++++++++- API/scriptChains.md | 15 +++++++++++++-- appendix/glossary.md | 12 ++++++++++++ changelog.md | 1 + 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/API/common.md b/API/common.md index 9e1b410..e6a601e 100644 --- a/API/common.md +++ b/API/common.md @@ -75,7 +75,7 @@ copyData(): CopyData; ```js apiServiceRequestInfo(): ApiService.RequestInfo | null; ``` -Возвращает ссылку на интерфейс [`ApiService.RequestInfo`](./apiService.md#request-info), если скрипт вызван через API Service, или `null` иначе. +Возвращает ссылку на интерфейс [`ApiService.RequestInfo`](./apiService.md#request-info), если скрипт вызван через API Service, или `null` иначе.   @@ -211,6 +211,9 @@ interface RequestManager { log(message: string, print?: boolean): this; logStatusMessage(message: string, print?: boolean): this; setStatusMessage(message: string): this; + getRequestId(): string | null; + getScriptName(): string | null; + getScriptLongId(): string | null; } ``` Интерфейс для записи в лог (устаревший функционал) и работы со статусными сообщениями. Все функции возвращают `this`. @@ -244,6 +247,27 @@ setStatusMessage(message: string): this;   +```js +getRequestId(): string | null; +``` +Каждый запуск скрипта должен существовать в рамках пользовательского или системного [запроса](../appendix/glossary.md#request). Метод предполагает возможность вернуть `null`, но такое поведение можно смело считать багом системы запуска скриптов. Возвращает идентификатор [запроса](../appendix/glossary.md#request), по которому можно найти запись о запуске скрипта в истории запуска скриптов в `web`-интерфейсе (`Macros` -> `Scripts` -> `Launch History`) и в панели администратора `Requests` -> `History`, если запрос на запуск скрипта был сделан пользователем. Скрипты, запущенные по расписанию или через систему API сервисов считаются системными. + +  + +```js +getScriptName(): string | null; +``` +Возвращает имя сущности текущего исполняемого скрипта кроме случая запуска сниппета кода с помощью метода [`ResultActionsInfo.makeCodeExecutionAction()`](./scriptChains.md#make-code-execution-action) — тогда вернёт `null`. + +  + +```js +getScriptLongId(): string | null; +``` +Возвращает [long id](#long-id) сущности текущего исполняемого скрипта кроме случая запуска сниппета кода с помощью метода [`ResultActionsInfo.makeCodeExecutionAction()`](./scriptChains.md#make-code-execution-action) — тогда вернёт `null`. + +  + ### Интерфейс ExportObfuscationState ```ts interface ExportObfuscationState { diff --git a/API/scriptChains.md b/API/scriptChains.md index 835d42f..7465f10 100644 --- a/API/scriptChains.md +++ b/API/scriptChains.md @@ -22,6 +22,7 @@ makeMacrosAction(identifier: string | number): ResultMacrosAction;   + ```js makeCodeExecutionAction(code: string): CodeExecutionAction; ``` @@ -154,6 +155,8 @@ appendAfter(): this; В такой ситуации скрипты исполнятся в следующем порядке: `3 -> 4 -> 5 -> 1 -> 2`. +Запущенный таким образом скрипт унаследует идентификатор [запроса](../appendix/glossary.md#request) от своего родителя. +   ```js @@ -290,7 +293,15 @@ setTaskDescription(description: string): this; ```js run(): TaskPromise | null; ``` -Запускает скрипт с помощью асинхронного механизма выполнения. Вызов метода породит задачу, которая не будет дожидаться завершения текущей задачи, а будет выполняться сразу. Так как родительская задача не завершается и может работать параллельно с дочерней, то важно следить за совместимостью режимов блокировок родительской и дочерней задач (иначе можно попасть в `dead lock`). На данный момент существует защита от погружения в бесконечную рекурсию и задача, запущенная через `run()`, не может сама использовать этот метод. Если до запуска скрипта был вызван метод `withPromise(true)`, возвращает [`TaskPromise`](#task-promise), иначе — `null`. +Запускает скрипт с помощью асинхронного механизма выполнения. Вызов метода породит задачу, которая не будет дожидаться завершения текущей задачи, а будет выполняться сразу. + +Так как родительская задача не завершается и может работать параллельно с дочерней, то важно следить за совместимостью режимов блокировок родительской и дочерней задач (иначе можно попасть в `dead lock`). + +На данный момент существует защита от погружения в бесконечную рекурсию и задача, запущенная через `run()`, не может сама использовать этот метод. + +Запущенный таким образом скрипт будет считаться порожденным системой и получит системный идентификатоп [запроса](../appendix/glossary.md#request), даже если родительский скрипт был запущен пользователем. + +Если до запуска скрипта был вызван метод `withPromise(true)`, возвращает [`TaskPromise`](#task-promise), иначе — `null`.   @@ -310,7 +321,7 @@ interface CodeExecutionAction extends BaseCodeExecutionAction { setTimeLimit(value: number): this; } ``` -Интерфейс действия запуска динамического кода. Наследуется от [`BaseCodeExecutionAction`](#base-code-execution-action). +Интерфейс действия запуска динамического кода. Наследуется от [`BaseCodeExecutionAction`](#base-code-execution-action). Скрипты, порождённые этим интерфейсом, не имеют имени и [long id](./common.md#long-id).   diff --git a/appendix/glossary.md b/appendix/glossary.md index 69ddbd8..6d1348c 100644 --- a/appendix/glossary.md +++ b/appendix/glossary.md @@ -13,6 +13,18 @@ ***Рабочая директория скрипта*** – временная папка на сервере, которая является рабочей директорией скрипта. Скрипт ***НЕ*** может выйти за её пределы. Перед запуском скрипта создаётся эта папка, скрипт запускает команду [`chroot`](https://ru.wikipedia.org/wiki/chroot) в неё, исполняется сам, после чего папка удаляется. + +***Запрос*** – последовательность операций, запущенных пользователем или системой. Запрос чтения и модификации данных создаёт контекст, который содержит: + +- идентификатор запроса, время поступления и время завершения, +- сам исходный запрос, идентификатор пользователя, модель, в контексте которой он выполняется, +- список агентов (воркеров), которые назначены для выполнения запроса, +- список шагов выполнения запроса, +- результат запроса (ответ на запрос). + +Запросы могут быть порождены пользователями, тогда они имеют идентификаторы вида `%латинские буквы и цифры%-%число, отличное от ноля%`. Если запрос порождён системой, то он будет иметь идентификатор вида `%латинские буквы и цифры%-0`. + +Запрос регистрируется в системе и может быть отменён, а также запрос будет залогирован в системные журналы. Список активных запросов можно увидеть в панели администратора в разделе `Requests` -> `Requests`. Рядом находится вкладка истории **пользовательских** запросов (системных запросов там не найти) — `History`. [Приложения](appendix.md) diff --git a/changelog.md b/changelog.md index 3a52fd7..98622e6 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ | Дата релиза | Тег релиза | Версия ScriptAPI | Версии MiddleWork | Версия приложения | Изменения | | --- | --- | --- | --- | --- | --- | +| xx.xx.xxxx | [v9.300.x.x](https://github.com/optimacros/scripts_documentation/tree/v9.300.x.x) | — |
  • 9.300.x.x
|
|
  • В интерфейс информации о запросе [`RequestManager`](./API/common.md#request-manager) добавлены методы для получения информации о текущем исполняемом скрипте: `getRequestId()`, `getScriptName()`, `getScriptLongId()`
| | 16.12.2024 | [v9.?00.x.x](https://github.com/optimacros/scripts_documentation/tree/v9.?00.x.x) | — |
  • 9.?00.x.x
|
|
  • Интерфейс работы с лицензиями воркспейса `EnterpriseLicenseManager` заменён на новый интерфейс работы с данными договора о параметрах воркспейса [EnterpriseContractManager](./API/common.md#enterprise-contract-manager)
| | 15.11.2024 | [9.200.x.13](https://github.com/optimacros/scripts_documentation/tree/v9.200.x.13) | — |
  • 9.200.dev.13
|
  • 9.200.x.x
|
  • В интерфейс [Filesystem](./API/fs.md#filesystem) добавлен метод для изменения кодировки файла `changeTextFileCharset()`
| | 26.09.2024 | [v9.0.50.6](https://github.com/optimacros/scripts_documentation/tree/v9.0.50.6) | — |
  • 9.0.50.6
|
  • 9.54.10
|
  • В интерфейс [Connectors](./API/connectors.md#connectors) добавлен коннектор для подключения к базе данных `Vertica`
| From 9711ea99a68ede0b6b54fd40cb34967079318fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BE=D0=B2?= Date: Tue, 29 Apr 2025 11:48:38 +0300 Subject: [PATCH 15/37] changelog, new file was added to the docx content --- API/readingGrid.md | 2 +- changelog.md | 1 + publish/contents.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/API/readingGrid.md b/API/readingGrid.md index 18290a8..a3bdb13 100644 --- a/API/readingGrid.md +++ b/API/readingGrid.md @@ -282,10 +282,10 @@ rows(): LabelsGroup | null;   + ```js dropDown(): Labels; ``` - Этот метод признан устаревшим. Вместо него стоит использовать метод [`dropDownSelector()`](#cell.dropdown-selector). Возвращает набор заголовков строк [`Labels`](#labels) выпадающего списка, который в интерфейсе пользователя `Optimacros` можно получить кликом по треугольнику внутри ячейки. Эта функция считается неэффективной, так как выгружает справочник целиком. Лучше зайти в нужный справочник и итерироваться по нему. diff --git a/changelog.md b/changelog.md index 2bee552..cab1b0a 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ | Дата релиза | Тег релиза | Версия ScriptAPI | Версии MiddleWork | Версия приложения | Изменения | | --- | --- | --- | --- | --- | --- | +| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Интерфейсы доступа к содержимому грида — заголовкам и ячейкам — были вынесены в отдельный файл — [readingGrid.md](./API/readingGrid.md)
  • Добавлен интерфейс постраничного получения списка опций значений клетки [DropDownSelector](./API/readingGrid.md#dropdown-selector) и метод для получения доступа к нему — [`Cell.dropDownSelector()`](./API/readingGrid.md#cell.dropdown-selector)
  • Метод получения списка опций значений клетки [`Cell.dropDown()`](./API/readingGrid.md#cell.dropdown) признан устаревшим
| | 24.03.2025 | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейсе [Filesystem](./API/fs.md#filesystem) изменены декларации функций `delete()`, `rename()`, `copy()`, `createDir()`, `deleteDir()`, `getSize()`
  • В интерфейс [CellBuffer](./API/common.md#cell-buffer) добавлена функция `lastApplyErrors()`
  • В интерфейсе [Importer](./API/exportImport.md#importer) исправлены исключения, которые могут бросать функции
| | 16.12.2024 | [v9.?00.x.x](https://github.com/optimacros/scripts_documentation/tree/v9.?00.x.x) | — |
  • 9.?00.x.x
|
|
  • Интерфейс работы с лицензиями воркспейса `EnterpriseLicenseManager` заменён на новый интерфейс работы с данными договора о параметрах воркспейса [EnterpriseContractManager](./API/common.md#enterprise-contract-manager)
| | 15.11.2024 | [9.200.x.13](https://github.com/optimacros/scripts_documentation/tree/v9.200.x.13) | — |
  • 9.200.dev.13
|
  • 9.200.x.x
|
  • В интерфейс [Filesystem](./API/fs.md#filesystem) добавлен метод для изменения кодировки файла `changeTextFileCharset()`
| diff --git a/publish/contents.json b/publish/contents.json index b2bf887..5e27cd9 100644 --- a/publish/contents.json +++ b/publish/contents.json @@ -9,6 +9,7 @@ "env.md", "variables.md", "views.md", + "readingGrid.md", "cubeCell.md", "dimensions.md", "elementsManipulator.md", From 678b788b32ac4a42c1e9254d63069a86aed452fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?= =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BE=D0=B2?= Date: Tue, 29 Apr 2025 13:38:39 +0300 Subject: [PATCH 16/37] getCollection docs --- API/common.md | 5 +++-- API/scripts.om.d.ts | 2 +- changelog.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/API/common.md b/API/common.md index 82d87b4..2ef60db 100644 --- a/API/common.md +++ b/API/common.md @@ -747,10 +747,11 @@ get(longId: number): EntityInfo | null;   + ```js -getCollection(longId: number[]): EntityInfo[]; +getCollection(longId: number[]): (EntityInfo | null)[]; ``` -Возвращает массив сущностей [`EntityInfo`](#entity-info) по массиву их [`longId`](#long-id). Корректно работает, только если все переданные `longId` корректные (существуют в модели). Иначе возвращает массив меньшей размерности. Использовать с осторожностью. Порядок возвращаемых сущностей `EntityInfo` может отличаться от порядка переданных `longId`. +Возвращает массив сущностей [`EntityInfo`](#entity-info) параллельный массиву их [`longId`](#long-id). Если сущность не найдена, на её месте будет возвращён `null`.   diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 498db23..2091dbb 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -783,7 +783,7 @@ export interface ResultInfo { export interface EntitiesInfo { get(longId: number): EntityInfo | null; - getCollection(longId: number[]): EntityInfo[]; + getCollection(longId: number[]): (EntityInfo | null)[]; } export interface CopyData { diff --git a/changelog.md b/changelog.md index cab1b0a..c73930e 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,7 @@ | Дата релиза | Тег релиза | Версия ScriptAPI | Версии MiddleWork | Версия приложения | Изменения | | --- | --- | --- | --- | --- | --- | -| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Интерфейсы доступа к содержимому грида — заголовкам и ячейкам — были вынесены в отдельный файл — [readingGrid.md](./API/readingGrid.md)
  • Добавлен интерфейс постраничного получения списка опций значений клетки [DropDownSelector](./API/readingGrid.md#dropdown-selector) и метод для получения доступа к нему — [`Cell.dropDownSelector()`](./API/readingGrid.md#cell.dropdown-selector)
  • Метод получения списка опций значений клетки [`Cell.dropDown()`](./API/readingGrid.md#cell.dropdown) признан устаревшим
| +| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Интерфейсы доступа к содержимому грида — заголовкам и ячейкам — были вынесены в отдельный файл — [readingGrid.md](./API/readingGrid.md)
  • Был **обратно несовместимо** переработан метод [`EntitiesInfo.getCollection()`](./API/common.md#entities-info.get-collection)
  • Добавлен интерфейс постраничного получения списка опций значений клетки [DropDownSelector](./API/readingGrid.md#dropdown-selector) и метод для получения доступа к нему — [`Cell.dropDownSelector()`](./API/readingGrid.md#cell.dropdown-selector)
  • Метод получения списка опций значений клетки [`Cell.dropDown()`](./API/readingGrid.md#cell.dropdown) признан устаревшим
| | 24.03.2025 | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейсе [Filesystem](./API/fs.md#filesystem) изменены декларации функций `delete()`, `rename()`, `copy()`, `createDir()`, `deleteDir()`, `getSize()`
  • В интерфейс [CellBuffer](./API/common.md#cell-buffer) добавлена функция `lastApplyErrors()`
  • В интерфейсе [Importer](./API/exportImport.md#importer) исправлены исключения, которые могут бросать функции
| | 16.12.2024 | [v9.?00.x.x](https://github.com/optimacros/scripts_documentation/tree/v9.?00.x.x) | — |
  • 9.?00.x.x
|
|
  • Интерфейс работы с лицензиями воркспейса `EnterpriseLicenseManager` заменён на новый интерфейс работы с данными договора о параметрах воркспейса [EnterpriseContractManager](./API/common.md#enterprise-contract-manager)
| | 15.11.2024 | [9.200.x.13](https://github.com/optimacros/scripts_documentation/tree/v9.200.x.13) | — |
  • 9.200.dev.13
|
  • 9.200.x.x
|
  • В интерфейс [Filesystem](./API/fs.md#filesystem) добавлен метод для изменения кодировки файла `changeTextFileCharset()`
| From de38fec46f4cbe0589c23248e70b673e264d679f Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Wed, 30 Apr 2025 15:34:42 +0700 Subject: [PATCH 17/37] - Docs for secrets is updated --- API/connectors.md | 7 -- API/scripts.om.d.ts | 89 +++++++++++----------- API/secrets.md | 182 ++++++++++++++++++++------------------------ 3 files changed, 127 insertions(+), 151 deletions(-) diff --git a/API/connectors.md b/API/connectors.md index 0c5497f..0b77ded 100644 --- a/API/connectors.md +++ b/API/connectors.md @@ -84,13 +84,6 @@ verticaViaPgsqlDriver(): PgsqlDrivenVerticaConnectorBuilder;   -```js -secrets(): SecretStorage; -``` -Возвращает объект для взаимодействия с [защищенным хранилищем секретов](./apiSecrets.md) - -  - [API Reference](API.md) [Оглавление](../README.md) diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index f879eca..fbde381 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -787,20 +787,20 @@ export interface BinaryData { } export interface Crypto { - sha1(data: string|SecretValue): string; + sha1(data: string | SecretValue): string; /** * * @param algo available values can be retrieved by getHashAlgorithms() * @param binary defaults to false */ - hash(algo: string , data: string|SecretValue , binary?: boolean): string | BinaryData + hash(algo: string , data: string | SecretValue , binary?: boolean): string | BinaryData /** * * @param algo available values can be retrieved by getHmacHashAlgorithms() * @param binary defaults to false */ - hmac(algo: string, data: string|SecretValue, key: string | BinaryData, binary?: boolean): string | BinaryData; + hmac(algo: string, data: string | SecretValue, key: string | BinaryData, binary?: boolean): string | BinaryData; getHashAlgorithms(): string[]; getHmacAlgorithms(): string[]; @@ -872,16 +872,16 @@ export interface BaseAdapter { } export interface FTPAdapter extends BaseAdapter { - setHost(host: string|SecretValue): this; + setHost(host: string | SecretValue): this; getHost(): string; - setPort(port: number|SecretValue): this; + setPort(port: number | SecretValue): this; getPort(): number; - setUsername(username: string|SecretValue): this + setUsername(username: string | SecretValue): this getUsername(): string | null; - setPassword(password: string|SecretValue): this; + setPassword(password: string | SecretValue): this; getPassword(): string | null; setRoot(root: string): this; @@ -981,11 +981,11 @@ export interface SqlConnection { } export interface SqlConnectorBuilder { - setHost(value: string|SecretValue): this; - setPort(value: number|SecretValue): this; - setUsername(value: string|SecretValue): this; - setPassword(value: string|SecretValue): this; - setDatabase(value: string|SecretValue): this; + setHost(value: string | SecretValue): this; + setPort(value: number | SecretValue): this; + setUsername(value: string | SecretValue): this; + setPassword(value: string | SecretValue): this; + setDatabase(value: string | SecretValue): this; load(): SqlConnection; } @@ -1001,31 +1001,31 @@ export interface SqlBulkCopyBuilder { * -S * @param value */ - setServerName(value: string|SecretValue): this; + setServerName(value: string | SecretValue): this; /** * Port is part of server name * @param value */ - setPort(value: number|SecretValue): this; + setPort(value: number | SecretValue): this; /** * -U * @param value */ - setUsername(value: string|SecretValue): this; + setUsername(value: string | SecretValue): this; /** * -P * @param value */ - setPassword(value: string|SecretValue): this; + setPassword(value: string | SecretValue): this; /** * -d * @param value */ - setDatabase(value: string|SecretValue): this; + setDatabase(value: string | SecretValue): this; /** * Query for export or table query string for import @@ -1211,9 +1211,9 @@ export interface OracleImportBuilder { } export interface OracleConnectorBuilder extends SqlConnectorBuilder { - setServiceName(value: string|SecretValue): this; - setSchema(value: string|SecretValue): this; - setTNS(value: string|SecretValue): this + setServiceName(value: string | SecretValue): this; + setSchema(value: string | SecretValue): this; + setTNS(value: string | SecretValue): this loadImportBuilder(): OracleImportBuilder; } @@ -1323,8 +1323,8 @@ export namespace Mongodb { } export interface ConnectorBuilder { - setDSN(value: string|SecretValue): this; - setDatabase(value: string|SecretValue): this; + setDSN(value: string | SecretValue): this; + setDatabase(value: string | SecretValue): this; load(): Connection; } } @@ -1391,28 +1391,28 @@ export namespace Http { } export interface Url { - setUrl(url: string|SecretValue): boolean; + setUrl(url: string | SecretValue): boolean; getUrl(): string; - setUrlPath(path: string|SecretValue): boolean; + setUrlPath(path: string | SecretValue): boolean; getUrlPath(): string; - setUrlScheme(scheme: string|SecretValue): boolean; + setUrlScheme(scheme: string | SecretValue): boolean; getUrlScheme(): string; - setHost(host: string|SecretValue): boolean; + setHost(host: string | SecretValue): boolean; getHost(): string; setPort(port: number | string | SecretValue): boolean; getPort(): number | null; - setUser(user: string|SecretValue): boolean; + setUser(user: string | SecretValue): boolean; getUser(): string | null; - setPassword(password: string|SecretValue): boolean + setPassword(password: string | SecretValue): boolean getPassword(): string | null; - setFragment(fragment: string|SecretValue): boolean; + setFragment(fragment: string | SecretValue): boolean; getFragment(): string | null; params(): UrlParams; @@ -1454,8 +1454,8 @@ export namespace Http { } export interface HttpAuth { - setUser(user: string|SecretValue): this; - setPassword(password: string|SecretValue): this; + setUser(user: string | SecretValue): this; + setPassword(password: string | SecretValue): this; /** * @param type basic|digest|ntlm */ @@ -1584,8 +1584,8 @@ export namespace WinAgent { } export interface WinAgentBuilder { - setCommandUrl(url: string|SecretValue): this; - setDownloadUrl(url: string|SecretValue): this; + setCommandUrl(url: string | SecretValue): this; + setDownloadUrl(url: string | SecretValue): this; auth(): Http.HttpAuth; setConnectTimeout(sec: number): this; setRequestTimeout(sec: number): this; @@ -1631,8 +1631,7 @@ export interface MysqlImportBuilder { export interface PostgresqlImportBuilder { setTable(name: string): this; - setSchema(name: string): this; - setSchema(name: string|SecretValue): this; + setSchema(name: string | SecretValue): this; setDelimiter(delimiter: string): this; setEnclosure(enclosure: string): this; setEscape(escape: string): this; @@ -1655,17 +1654,17 @@ export interface PgsqlDrivenVerticaConnectorBuilder extends PostgresqlConnectorB export interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { setAccount(account: string): this; - setAccount(account: string|SecretValue): this; - setRegion(region: string|SecretValue): this; + setAccount(account: string | SecretValue): this; + setRegion(region: string | SecretValue): this; /** * Configuring OCSP Checking * Default is false * @param insecure */ setInsecure(insecure: boolean): this; - setWarehouse(warehouse: string|SecretValue): this; - setSchema(schema: string|SecretValue): this; - setRole(role: string|SecretValue): this; + setWarehouse(warehouse: string | SecretValue): this; + setSchema(schema: string | SecretValue): this; + setRole(role: string | SecretValue): this; setProtocol(protocol: string): this; } @@ -1778,17 +1777,17 @@ export interface ModelUsersTab extends Tab { } export interface Secrets { - getStorage(vaultId: string): SecretStorage; + getStorage(vaultId: string): SecretStorage; } export interface SecretStorage { - getSecret(path: string, key: string): SecretValue + getSecret(path: string, key: string): SecretValue; } export interface SecretValue { - getStorageIdentifier(): string; - getIdentifier(): string; - toJson(): object; + getStorageIdentifier(): string; + getIdentifier(): string; + toJson(): Object; } export interface OM { diff --git a/API/secrets.md b/API/secrets.md index 35d5792..c42d67f 100644 --- a/API/secrets.md +++ b/API/secrets.md @@ -1,148 +1,132 @@ -# Поддержка работы с секретами +# Секреты -Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом [OpenBao](https://openbao.org/), который поставляется с дистрибутивом. У сервиса есть UI (/openbao/ui). Через него клиенты записывают в защищенное хранилище важные данные, чтобы затем использовать в скриптах. +Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом [OpenBao](https://openbao.org/), который поставляется с дистрибутивом. У сервиса есть UI `workspace_url/openbao/ui`. Через него клиенты записывают в защищенное хранилище важные данные, чтобы затем использовать в скриптах. Такие данные называются **секретами**. Они бывают разных видов. Поддерживаемые типы: -- OpenBaoKeyValueSecret (ключ-значение). +- `OpenBaoKeyValueSecret` - ключ-значение -## Quick start +## Интерфейс Secrets +```ts +interface Secrets { + getStorage(vaultId: string): SecretStorage; +} +``` +Интерфейс для доступа к секретам. -Посмотрим на пример получения секрета из хранилища и разберем, что здесь происходит: +  -```typescript -let secret = om.secrets.getStorage('openbao-vault').getSecret('it-gets-secret-path', 'it-gets-secret-key') +```js +getStorage(vaultId: string): SecretStorage; ``` +Возвращает хранилище [`SecretStorage`](#secret-storage) с идентификатором `vaultId`. -Интерфейс **secrets** представляет метод `getStorage()`. В него передается идентификатор хранилища. Доступ к нему настраивается в манифесте workspace'а администраторами. - -К одному workspace'у могут быть подключены сразу несколько хранилищ. В скрипте можно обращаться к любому из них или сразу к нескольким. +К воркспейсу могут быть подключены сразу несколько хранилищ. Доступ настраивается в манифесте воркспейса администратором. Поддерживаются решения [OpenBao](https://openbao.org/) и [Hashicorp Vault](https://www.vaultproject.io/). Список подключенных хранилищ и их идентификаторы `id` можно посмотреть в панели администратора воркспейса в разделе `Secrets`. В одном скрипте можно обращаться к любому из них или сразу к нескольким. -```typescript -let baoSecret = om.secrets.getStorage('openbao-vault').getSecret( - 'several-vaults-single-script-openbao-path', - 'several-vaults-single-script-openbao-key' -) +  -let hashiSecret = om.secrets.getStorage('hashicorp-vault').getSecret( - 'several-vaults-single-script-hashicorp-path', - 'several-vaults-single-script-hashicorp-key' -) +## Интерфейс SecretStorage +```ts +interface SecretStorage { + getSecret(path: string, key: string): SecretValue; +} ``` +Интерфейс для работы с защищенным хранилищем секретов. -Поддерживаются решения [OpenBao](https://openbao.org/) и [Hashicorp Vault](https://www.vaultproject.io/). Список подключенных хранилищ можно посмотреть в админке в разделе `/secrets`. +  -### Структура секретов +```js +getSecret(path: string, key: string): SecretValue; +``` +Возвращает секрет [`SecretValue`](#secret-value). Секреты в хранилищах хранятся в иерархиях. - ``` -/secret/path/to/secrets +/secret/path-to-secrets |--- secret-key-1 |--- secret-key-2 ``` -Найти секрет можно, зная "папку", в которой он лежит (путь) и название самого секрета (ключ). Поэтому методу `getSecret()` нужны в качестве аргументов оба параметра. +Получить секрет можно, зная "папку" `path`, в которой он лежит и название ключа секрета `key`. + +  -Обратившись к хранилищу за секретом, вы получите объект SecretValue. Его можно преобразовать в JSON-объект и работать с ним обычным способом. +## Интерфейс SecretValue +```ts +interface SecretValue { + getStorageIdentifier(): string; + getIdentifier(): string; + toJson(): Object; +} +``` +Объект секрета. **Обратите внимание, что значения секрета в объекте нет.** Он только содержит информацию о секрете. Так сделано для безопасности. Значения считываются из хранилища только внутри приложения Optimacros. Этот объект нужно передавать в методы API скриптов, которые поддерживают секреты, вместо простых типов данных. Приложение само сделает запрос в хранилище и подставит значение секрета. -```typescript +Опознать методы с поддержкой секретов можно по сигнатуре `setPassword(password: string | SecretValue): this;`. Появляется выбор - использовать простой тип данных или секрет. Проверка типа значения также работает на секретах. Если тип значения не совпадет с тем, который ожидается методом, вылетит ошибка. -console.log(secret.toJson().type); -console.log(secret.toJson().params.key); -console.log(secret.toJson().params.value); +  + +```js +toJson(): Object; +``` +Возвращает JSON-объект секрета вида +```ts { "type":"OpenBaoKeyValueSecret", "params": { - "key":"it-gets-secret-key", - "path":"it-gets-secret-path" - } + "key":"secret-key", + "path":"secret-path", + }, } ``` +Такой JSON-объект можно создать самостоятельно и передавать в методы API скриптов с поддержкой секретов. -Обратите внимание, что значения секрета в объекте нет. Так сделано для безопасности. Значения раскрываются только на backend'е. Передавайте в доступные методы API объекты секретов. Backend сам сделает запрос в хранилище и подставит значение секрета, где это нужно. +  -```typescript -let secret = om.secrets.getStorage('openbao-vault').getSecret('ftp-adapter-path', 'ftp-adapter-port') +##Примеры -let fs = om.filesystems.ftp(); +Получение секрета из хранилища и передача его в метод API скриптов +```ts +const secret = om.secrets.getStorage('openbao-vault').getSecret('ftp-connection', 'host'); -fs.setPort(secret); -``` +const ftp = om.filesystems.ftp(); -Не обязательно явно забирать секрет из хранилища, чтобы передать его на backend. Можно самостоятельно собрать неклассифицированный объект V8 и воспользоваться им. +ftp.setHost(secret); +``` -```typescript -const NCSecret = { +Самостоятельное создание JSON-объекта секрета и передача его в метод API скриптов +```ts +const secret = { "type": "OpenBaoKeyValueSecret", "params": { "storageIdentifier": "openbao-vault", - "path": "nc-secret-port-path", - "key": "nc-secret-port-key" - } -} - -let fs = om.filesystems.ftp(); -fs.setPort(NCSecret); -``` - -Валидация по-прежнему работает в методах API, даже на значениях секретов. Если тип значения не совпадет с тем, который ожидается методом API, вылетит ошибка. + "path": "ftp-connection", + "key": "host", + }, +}; -```typescript -let secret = om.secrets.getStorage('openbao-vault').getSecret('ftp-bad-secret-path', 'ftp-bad-secret-port') +const ftp = om.filesystems.ftp(); -let fs = om.filesystems.ftp(); - -fs.setPort(secret); -console.log(fs.getPort()); +ftp.setHost(secret); ``` -### Use-кейсы +Передача секрета в дочерний скрипт в составе JSON-oбъекта +```ts +const secret = om.secrets.getStorage('openbao-vault').getSecret('secret-path', 'secret-key'); -Объекты секретов можно передавать в качестве env-параметров из одних скриптов в другие. +const ENV = { + SECRET: secret.toJson(), +}; -```typescript -const targetScript = om.common.resultInfo() +om.common.resultInfo() .actionsInfo() - .makeMacrosAction('testChainEnvironment_2'); - -const envSecret = om.secrets.getStorage('openbao-vault').getSecret('env-chainset-path', 'env-chainset-key') - -targetScript.environmentInfo().set('ENV_SECRET', envSecret); -targetScript.appendAfter(); + .makeMacrosAction('next script') + .appendAfter() + .environmentInfo() + .set('ENV', ENV); ``` -### Интерфейсы и изменения в API +  -Интерфейсы функционала описаны в декларации - -```typescript -export interface Secrets { - getStorage(vaultId: string): SecretStorage; -} +[API Reference](./API.md) -export interface SecretStorage { - getSecret(path: string, key: string): SecretValue -} - -export interface SecretValue { - getStorageIdentifier(): string; - getIdentifier(): string; - toJson(): object; -} -``` - -В некоторые методы, добавлена поддержка секретов. Прежний код, передающий в них скалярные аргументы, должен работать без изменений. - -У вас появляется выбор - можно использовать и скалярные типы, и секреты. Опознать методы с новыми возможностями можно по изменениям в сигнатурах. - -```typescript - -export interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { - setAccount(account: string|SecretValue): SnowflakeConnectorBuilder; - - setRegion(region: string|SecretValue): SnowflakeConnectorBuilder; - - // ... -} -``` +[Оглавление](../README.md) \ No newline at end of file From 4e97cdb6873f1df16fa565a0035652bd10904329 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Wed, 30 Apr 2025 15:50:02 +0700 Subject: [PATCH 18/37] Small corrections --- API/scripts.om.d.ts | 9 ++++----- API/secrets.md | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index fbde381..1578427 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -794,7 +794,7 @@ export interface Crypto { * @param algo available values can be retrieved by getHashAlgorithms() * @param binary defaults to false */ - hash(algo: string , data: string | SecretValue , binary?: boolean): string | BinaryData + hash(algo: string, data: string | SecretValue , binary?: boolean): string | BinaryData; /** * * @param algo available values can be retrieved by getHmacHashAlgorithms() @@ -878,7 +878,7 @@ export interface FTPAdapter extends BaseAdapter { setPort(port: number | SecretValue): this; getPort(): number; - setUsername(username: string | SecretValue): this + setUsername(username: string | SecretValue): this; getUsername(): string | null; setPassword(password: string | SecretValue): this; @@ -1213,7 +1213,7 @@ export interface OracleImportBuilder { export interface OracleConnectorBuilder extends SqlConnectorBuilder { setServiceName(value: string | SecretValue): this; setSchema(value: string | SecretValue): this; - setTNS(value: string | SecretValue): this + setTNS(value: string | SecretValue): this; loadImportBuilder(): OracleImportBuilder; } @@ -1409,7 +1409,7 @@ export namespace Http { setUser(user: string | SecretValue): boolean; getUser(): string | null; - setPassword(password: string | SecretValue): boolean + setPassword(password: string | SecretValue): boolean; getPassword(): string | null; setFragment(fragment: string | SecretValue): boolean; @@ -1653,7 +1653,6 @@ export interface PgsqlDrivenVerticaConnectorBuilder extends PostgresqlConnectorB } export interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { - setAccount(account: string): this; setAccount(account: string | SecretValue): this; setRegion(region: string | SecretValue): this; /** diff --git a/API/secrets.md b/API/secrets.md index c42d67f..ea7421e 100644 --- a/API/secrets.md +++ b/API/secrets.md @@ -1,6 +1,6 @@ # Секреты -Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом [OpenBao](https://openbao.org/), который поставляется с дистрибутивом. У сервиса есть UI `workspace_url/openbao/ui`. Через него клиенты записывают в защищенное хранилище важные данные, чтобы затем использовать в скриптах. +Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом [OpenBao](https://openbao.org/), который поставляется с дистрибутивом. У сервиса есть UI: `workspace_url/openbao/ui`. Через него клиенты записывают в защищенное хранилище важные данные, чтобы затем использовать в скриптах. Такие данные называются **секретами**. Они бывают разных видов. Поддерживаемые типы: - `OpenBaoKeyValueSecret` - ключ-значение @@ -46,7 +46,7 @@ getSecret(path: string, key: string): SecretValue; |--- secret-key-2 ``` -Получить секрет можно, зная "папку" `path`, в которой он лежит и название ключа секрета `key`. +Получить секрет можно, зная "папку" `path`, в которой он лежит, и название ключа секрета `key`.   @@ -60,7 +60,7 @@ interface SecretValue { ``` Объект секрета. **Обратите внимание, что значения секрета в объекте нет.** Он только содержит информацию о секрете. Так сделано для безопасности. Значения считываются из хранилища только внутри приложения Optimacros. Этот объект нужно передавать в методы API скриптов, которые поддерживают секреты, вместо простых типов данных. Приложение само сделает запрос в хранилище и подставит значение секрета. -Опознать методы с поддержкой секретов можно по сигнатуре `setPassword(password: string | SecretValue): this;`. Появляется выбор - использовать простой тип данных или секрет. Проверка типа значения также работает на секретах. Если тип значения не совпадет с тем, который ожидается методом, вылетит ошибка. +Опознать методы с поддержкой секретов можно по сигнатуре `setPassword(password: string | SecretValue): this;`. Появляется выбор - использовать простой тип данных или секрет. Проверка типа значения также работает на секретах. Если тип не совпадет с тем, который ожидается методом, вылетит ошибка.   @@ -82,7 +82,7 @@ toJson(): Object;   -##Примеры +## Примеры Получение секрета из хранилища и передача его в метод API скриптов ```ts From 31cedb550160ef206c846306c20b0fd172494e46 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Wed, 30 Apr 2025 15:53:53 +0700 Subject: [PATCH 19/37] - Line breaks between examples --- API/secrets.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/API/secrets.md b/API/secrets.md index ea7421e..4544ead 100644 --- a/API/secrets.md +++ b/API/secrets.md @@ -93,6 +93,8 @@ const ftp = om.filesystems.ftp(); ftp.setHost(secret); ``` +  + Самостоятельное создание JSON-объекта секрета и передача его в метод API скриптов ```ts const secret = { @@ -109,6 +111,8 @@ const ftp = om.filesystems.ftp(); ftp.setHost(secret); ``` +  + Передача секрета в дочерний скрипт в составе JSON-oбъекта ```ts const secret = om.secrets.getStorage('openbao-vault').getSecret('secret-path', 'secret-key'); From a463b537f98a19d5c29aa2bcc6b81aec1f8930d5 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Wed, 30 Apr 2025 23:41:20 +0700 Subject: [PATCH 20/37] - Updates for methods with secret support --- API/crypto.md | 18 +++++----- API/fs.md | 18 +++++----- API/http.md | 44 +++++++++++------------ API/mongoDB.md | 10 +++--- API/relationalDB.md | 88 ++++++++++++++++++++++----------------------- API/secrets.md | 2 +- API/winAgent.md | 10 +++--- 7 files changed, 95 insertions(+), 95 deletions(-) diff --git a/API/crypto.md b/API/crypto.md index 7af38cd..9489675 100644 --- a/API/crypto.md +++ b/API/crypto.md @@ -5,9 +5,9 @@ ## Интерфейс Crypto ```ts interface Crypto { - sha1(data: string): string; - hash(algo: string, data: string, binary?: boolean): string | BinaryData; - hmac(algo: string, data: string, key: string | BinaryData, binary?: boolean): string | BinaryData; + sha1(data: string | SecretValue): string; + hash(algo: string, data: string | SecretValue, binary?: boolean): string | BinaryData; + hmac(algo: string, data: string | SecretValue, key: string | BinaryData, binary?: boolean): string | BinaryData; getHashAlgorithms(): string[]; getHmacHashAlgorithms(): string[]; } @@ -17,9 +17,9 @@ interface Crypto {   ```js -sha1(data: string): string; +sha1(data: string | SecretValue): string; ``` -Возвращает [`SHA1-хэш`](https://en.wikipedia.org/wiki/SHA-1) строки `data` (переданной в кодировке `UTF-8`), вычисленный по алгоритму `US Secure Hash Algorithm 1` в виде `40`-символьного шестнадцатеричного числа. +Возвращает [`SHA1-хэш`](https://en.wikipedia.org/wiki/SHA-1) строки `data` (переданной в кодировке `UTF-8`), вычисленный по алгоритму `US Secure Hash Algorithm 1` в виде `40`-символьного шестнадцатеричного числа. Поддерживает [секреты](./secrets.md). Пример использования: @@ -34,9 +34,9 @@ console.log(   ```js -hash(algo: string, data: string, binary?: boolean): string | BinaryData; +hash(algo: string, data: string | SecretValue, binary?: boolean): string | BinaryData; ``` -Метод для получения хэша строки `data` (переданной в кодировке `UTF-8`) по указанному алгоритму `algo`. Полный список доступных алгоритмов может быть получен с помощью метода `getHashAlgorithms()`. +Метод для получения хэша строки `data` (переданной в кодировке `UTF-8`) по указанному алгоритму `algo`. Поддерживает [секреты](./secrets.md). Полный список доступных алгоритмов может быть получен с помощью метода `getHashAlgorithms()`. Если `binary = false` (по умолчанию), то хэш возвращается в виде строки, использующей шестнадцатеричное кодирование в нижнем регистре ([`hexits`](https://en.wiktionary.org/wiki/hexit)). @@ -54,9 +54,9 @@ console.log(   ```js -hmac(algo: string, data: string, key: string | BinaryData, binary?: boolean): string | BinaryData; +hmac(algo: string, data: string | SecretValue, key: string | BinaryData, binary?: boolean): string | BinaryData; ``` -Метод для получения подписи [`HMAC (Hash-based Message Authentication Code)`](https://ru.wikipedia.org/wiki/HMAC) для строки `data` (переданной в кодировке `UTF-8`) с использованием ключа `key` и алгоритма хэширования `algo`. +Метод для получения подписи [`HMAC (Hash-based Message Authentication Code)`](https://ru.wikipedia.org/wiki/HMAC) для строки `data` (переданной в кодировке `UTF-8`) с использованием ключа `key` и алгоритма хэширования `algo`. Поддерживает [секреты](./secrets.md). Ключ `key` может быть передан в виде строки в кодировке `UTF-8` или в бинарном виде — инкапсулированным в объект [`BinaryData`](#binarydata). Аналогично методу `hash()`, полный список доступных алгоритмов хэширования `algo` может быть получен с помощью метода `getHmacHashAlgorithms()`. diff --git a/API/fs.md b/API/fs.md index a316f54..baaa12e 100644 --- a/API/fs.md +++ b/API/fs.md @@ -322,16 +322,16 @@ load(): Filesystem; ### Интерфейс FTPAdapter ```ts interface FTPAdapter extends BaseAdapter { - setHost(host: string): this; + setHost(host: string | SecretValue): this; getHost(): string; - setPort(port: number): this; + setPort(port: number | SecretValue): this; getPort(): number; - setUsername(username: string): this; + setUsername(username: string | SecretValue): this; getUsername(): string | null; - setPassword(password: string): this; + setPassword(password: string | SecretValue): this; getPassword(): string | null; setRoot(root: string): this; @@ -354,12 +354,12 @@ interface FTPAdapter extends BaseAdapter { } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для соединения с сервером [`FTP`](https://ru.wikipedia.org/wiki/FTP). Наследуется от интерфейса [`BaseAdapter`](#base-adapter). +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для соединения с сервером [`FTP`](https://ru.wikipedia.org/wiki/FTP). Наследуется от интерфейса [`BaseAdapter`](#base-adapter). Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setHost(host: string): this; +setHost(host: string | SecretValue): this; ``` Устанавливает адрес хоста. По умолчанию: `''`. Возвращает `this`. @@ -373,7 +373,7 @@ getHost(): string;   ```js -setPort(port: number): this; +setPort(port: number | SecretValue): this; ``` Устанавливает номер порта. По умолчанию: `21`. Возвращает `this`. @@ -387,7 +387,7 @@ getPort(): number;   ```js -setUsername(username: string): this; +setUsername(username: string | SecretValue): this; ``` Устанавливает имя пользователя. Возвращает `this`. @@ -401,7 +401,7 @@ getUsername(): string | null;   ```js -setPassword(password: string): this; +setPassword(password: string | SecretValue): this; ``` Устанавливает пароль. Возвращает `this`. diff --git a/API/http.md b/API/http.md index d5d7fc3..2c800f1 100644 --- a/API/http.md +++ b/API/http.md @@ -274,39 +274,39 @@ fileBody(): FileRequestBody; ### Интерфейс Url ```ts interface Url { - setUrl(url: string): boolean; + setUrl(url: string | SecretValue): boolean; getUrl(): string; - setUrlPath(path: string): boolean; + setUrlPath(path: string | SecretValue): boolean; getUrlPath(): string; - setUrlScheme(scheme: string): boolean; + setUrlScheme(scheme: string | SecretValue): boolean; getUrlScheme(): string; - setHost(host: string): boolean; + setHost(host: string | SecretValue): boolean; getHost(): string; - setPort(port: number | string): boolean; + setPort(port: number | string | SecretValue): boolean; getPort(): number | null; - setUser(user: string): boolean; + setUser(user: string | SecretValue): boolean; getUser(): string | null; - setPassword(password: string): boolean; + setPassword(password: string | SecretValue): boolean; getPassword(): string | null; - setFragment(fragment: string): boolean; + setFragment(fragment: string | SecretValue): boolean; getFragment(): string | null; params(): UrlParams; } ``` -Интерфейс построения [`URL`](https://ru.wikipedia.org/wiki/URL). +Интерфейс построения [`URL`](https://ru.wikipedia.org/wiki/URL). Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setUrl(url: string): boolean; +setUrl(url: string | SecretValue): boolean; ``` Устанавливает URL целиком. Возвращает `true`. @@ -320,7 +320,7 @@ getUrl(): string;   ```js -setUrlPath(path: string): boolean; +setUrlPath(path: string | SecretValue): boolean; ``` Устанавливает путь на сервере. Значение по умолчанию: `'\'`. Возвращает `true`. @@ -334,7 +334,7 @@ getUrlPath(): string;   ```js -setUrlScheme(scheme: string): boolean; +setUrlScheme(scheme: string | SecretValue): boolean; ``` Устанавливает схему URL (протокол). Значение по умолчанию: `'http'`. Возвращает `true`. @@ -348,7 +348,7 @@ getUrlScheme(): string;   ```js -setHost(host: string): boolean; +setHost(host: string | SecretValue): boolean; ``` Устанавливает имя или адрес хоста. Значение по умолчанию: `''`. Возвращает `true`. @@ -362,7 +362,7 @@ getHost(): string;   ```js -setPort(port: number | string): boolean; +setPort(port: number | string | SecretValue): boolean; ``` Устанавливает номер порта. Возвращает `true`. @@ -376,7 +376,7 @@ getPort(): number | null;   ```js -setUser(user: string): boolean; +setUser(user: string | SecretValue): boolean; ``` Устанавливает имя пользователя. Возвращает `true`. @@ -390,7 +390,7 @@ getUser(): string | null;   ```js -setPassword(password: string): boolean; +setPassword(password: string | SecretValue): boolean; ``` Устанавливает пароль. Возвращает `true`. @@ -404,7 +404,7 @@ getPassword(): string | null;   ```js -setFragment(fragment: string): boolean; +setFragment(fragment: string | SecretValue): boolean; ``` Устанавливает идентификатор якоря. Возвращает `true`. @@ -520,25 +520,25 @@ getProtocols(): string[]; ### Интерфейс HttpAuth ```ts interface HttpAuth { - setUser(user: string): this; - setPassword(password: string): this; + setUser(user: string | SecretValue): this; + setPassword(password: string | SecretValue): this; setType(type: string): this; setStatus(status: boolean): this; } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для настроек аутентификации HTTP. Все функции возвращают `this`. +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для настроек аутентификации HTTP. Все функции возвращают `this`. Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setUser(user: string): this; +setUser(user: string | SecretValue): this; ``` Устанавливает имя пользователя.   ```js -setPassword(password: string): this; +setPassword(password: string | SecretValue): this; ``` Устанавливает пароль. diff --git a/API/mongoDB.md b/API/mongoDB.md index 9eb1e88..154caa3 100644 --- a/API/mongoDB.md +++ b/API/mongoDB.md @@ -7,24 +7,24 @@ ## Интерфейс ConnectorBuilder ```ts interface ConnectorBuilder { - setDSN(value: string): this; - setDatabase(value: string): this; + setDSN(value: string | SecretValue): this; + setDatabase(value: string | SecretValue): this; load(): Connection; } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для настройки подключения к [`MongoDB`](https://ru.wikipedia.org/wiki/MongoDB). +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для настройки подключения к [`MongoDB`](https://ru.wikipedia.org/wiki/MongoDB). Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setDSN(value: string): this; +setDSN(value: string | SecretValue): this; ``` Устанавливает [`DSN`](https://www.mongodb.com/docs/manual/reference/connection-string/) для подключения. См. [документацию](https://docs.mongodb.com/bi-connector/master/tutorial/create-system-dsn/) по созданию DSN. Возвращает `this`.   ```js -setDatabase(value: string): this; +setDatabase(value: string | SecretValue): this; ``` Устанавливает имя базы данных. Возвращает `this`. diff --git a/API/relationalDB.md b/API/relationalDB.md index 905e6f3..e444cc3 100644 --- a/API/relationalDB.md +++ b/API/relationalDB.md @@ -130,48 +130,48 @@ qb(): SqlQueryBuilder; ### Интерфейс SqlConnectorBuilder ```ts interface SqlConnectorBuilder { - setHost(value: string): this; - setPort(value: number): this; - setUsername(value: string): this; - setPassword(value: string): this; - setDatabase(value: string): this; + setHost(value: string | SecretValue): this; + setPort(value: number | SecretValue): this; + setUsername(value: string | SecretValue): this; + setPassword(value: string | SecretValue): this; + setDatabase(value: string | SecretValue): this; load(): SqlConnection; } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), базовый интерфейс [`коннекторов`](../appendix/glossary.md#connector) для подключения к реляционной базе данных. +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), базовый интерфейс [`коннекторов`](../appendix/glossary.md#connector) для подключения к реляционной базе данных. Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setHost(value: string): this; +setHost(value: string | SecretValue): this; ``` Устанавливает адрес подключения. Возвращает `this`.   ```js -setPort(value: number): this; +setPort(value: number | SecretValue): this; ``` Устанавливает номер порта для подключения. Возвращает `this`.   ```js -setUsername(value: string): this; +setUsername(value: string | SecretValue): this; ``` Устанавливает имя пользователя. Возвращает `this`.   ```js -setPassword(value: string): this; +setPassword(value: string | SecretValue): this; ``` Устанавливает пароль. Возвращает `this`.   ```js -setDatabase(value: string): this; +setDatabase(value: string | SecretValue): this; ``` Устанавливает имя базы данных. Возвращает `this`. @@ -281,32 +281,32 @@ loadBulkCopyBuilder(): SqlBulkCopyBuilder; ### Интерфейс OracleConnectorBuilder ```ts interface OracleConnectorBuilder extends SqlConnectorBuilder { - setServiceName(value: string): this; - setSchema(value: string): this; - setTNS(value: string): this; + setServiceName(value: string | SecretValue): this; + setSchema(value: string | SecretValue): this; + setTNS(value: string | SecretValue): this; loadImportBuilder(): OracleImportBuilder; } ``` -[`Коннектор`](../appendix/glossary.md#connector) для подключения к базе данных [`Oracle`](https://ru.wikipedia.org/wiki/Oracle_Database). Все функции возвращают `this`. Интерфейс наследуется от [`SqlConnectorBuilder`](#sql-connector-builder). +[`Коннектор`](../appendix/glossary.md#connector) для подключения к базе данных [`Oracle`](https://ru.wikipedia.org/wiki/Oracle_Database). Все функции возвращают `this`. Интерфейс наследуется от [`SqlConnectorBuilder`](#sql-connector-builder). Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setServiceName(value: string): this; +setServiceName(value: string | SecretValue): this; ``` Устанавливает имя службы (SERVICE_NAME). SERVICE_NAME определяет одно или ряд имен для подключения к одному экземпляру базы данных. Возможные значения SERVICE_NAME указываются в сетевых установках Oracle и регистрируются в качестве службы БД процессом listener.   ```js -setSchema(value: string): this; +setSchema(value: string | SecretValue): this; ``` Устанавливает [`схему`](https://docs.oracle.com/cd/E11882_01/server.112/e10897/schema.htm).   ```js -setTNS(value: string): this; +setTNS(value: string | SecretValue): this; ``` Устанавливает имя службы TNS. Протокол TNS (Transparent Network Substrate) — уровень связи, используемый базами данных Oracle. Имя службы TNS — это имя, с которым экземпляр базы данных Oracle представлен в сети. Имя службы TNS назначается при настройке подключений к базе данных Oracle. @@ -322,28 +322,28 @@ loadImportBuilder(): OracleImportBuilder; ### Интерфейс SnowflakeConnectorBuilder ```ts interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { - setAccount(account: string): this; - setRegion(region: string): this; + setAccount(account: string | SecretValue): this; + setRegion(region: string | SecretValue): this; setInsecure(insecure: boolean): this; - setWarehouse(warehouse: string): this; - setSchema(schema: string): this; - setRole(role: string): this; + setWarehouse(warehouse: string | SecretValue): this; + setSchema(schema: string | SecretValue): this; + setRole(role: string | SecretValue): this; setProtocol(protocol: string): this; } ``` -[`Коннектор`](../appendix/glossary.md#connector) для подключения к базе данных [`Snowflake`](https://en.wikipedia.org/wiki/Snowflake_Inc%2E) (для подключения используется [PHP PDO Driver](https://docs.snowflake.com/en/user-guide/php-pdo-driver.html)). Все функции возвращают `this`. Интерфейс наследуется от [`SqlConnectorBuilder`](#sql-connector-builder). +[`Коннектор`](../appendix/glossary.md#connector) для подключения к базе данных [`Snowflake`](https://en.wikipedia.org/wiki/Snowflake_Inc%2E) (для подключения используется [PHP PDO Driver](https://docs.snowflake.com/en/user-guide/php-pdo-driver.html)). Все функции возвращают `this`. Интерфейс наследуется от [`SqlConnectorBuilder`](#sql-connector-builder). Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setAccount(account: string): this; +setAccount(account: string | SecretValue): this; ``` Устанавливает [`имя аккаунта`](https://docs.snowflake.com/en/sql-reference/account-usage.html).   ```js -setRegion(region: string): this; +setRegion(region: string | SecretValue): this; ``` Устанавливает [`имя региона`](https://docs.snowflake.com/en/user-guide/admin-account-identifier.html). Опциональный. @@ -357,21 +357,21 @@ setInsecure(insecure: boolean): this;   ```js -setWarehouse(warehouse: string): this; +setWarehouse(warehouse: string | SecretValue): this; ``` Устанавливает [`название хранилища`](https://docs.snowflake.com/en/user-guide/warehouses-overview.html). Опциональный.   ```js -setSchema(value: string): this; +setSchema(value: string | SecretValue): this; ``` Устанавливает [`схему`](https://docs.snowflake.com/en/sql-reference/info-schema.html). Опциональный, по умолчанию `public`.   ```js -setRole(role: string): this; +setRole(role: string | SecretValue): this; ``` Устанавливает [`имя роли`](https://docs.snowflake.com/en/user-guide/security-access-control-overview.html#roles). Опциональный. @@ -602,7 +602,7 @@ getStats(): Object; ```js interface PostgresqlImportBuilder { setTable(name: string): this; - setSchema(name: string): this; + setSchema(name: string | SecretValue): this; setDelimiter(delimiter: string): this; setEnclosure(enclosure: string): this; setEscape(escape: string): this; @@ -624,9 +624,9 @@ setTable(name: string): this;   ```js -setSchema(name: string): this; +setSchema(name: string | SecretValue): this; ``` -Устанавливает [схему](https://www.postgresql.org/docs/current/ddl-schemas.html). +Устанавливает [схему](https://www.postgresql.org/docs/current/ddl-schemas.html). Поддерживает [секреты](./secrets.md).   @@ -716,11 +716,11 @@ getCommand(): string; ### Интерфейс SqlBulkCopyBuilder ```ts interface SqlBulkCopyBuilder { - setServerName(value: string): this; - setPort(value: number): this; - setUsername(value: string): this; - setPassword(value: string): this; - setDatabase(value: string): this; + setServerName(value: string | SecretValue): this; + setPort(value: number | SecretValue): this; + setUsername(value: string | SecretValue): this; + setPassword(value: string | SecretValue): this; + setDatabase(value: string | SecretValue): this; setQuery(value: string): this; setPacketSize(size: number): this; setBatchSize(size: number): this; @@ -752,7 +752,7 @@ interface SqlBulkCopyBuilder { format(path: string, xml: boolean): SqlBulkCopyResult; } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для импорта в СУБД MS SQL из файла CSV с помощью утилиты [*bcp*](https://docs.microsoft.com/ru-ru/sql/tools/bcp-utility). Все функции, начинающиеся с `set...()`, возвращают `this`. +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для импорта в СУБД MS SQL из файла CSV с помощью утилиты [*bcp*](https://docs.microsoft.com/ru-ru/sql/tools/bcp-utility). Все функции, начинающиеся с `set...()`, возвращают `this`. Функции для установки параметров подключения поддерживают [секреты](./secrets.md). Порядок полей в файле CSV и таблице должен строго совпадать, даже при импорте в таблицу с полем [`IDENTITY`](https://docs.microsoft.com/ru-ru/sql/t-sql/statements/create-table-transact-sql-identity-property), так как в утилите *bcp* имеется баг, из-за которого работоспособность функций [`setFormatFile()`](#sql-bulk-copy-builder.set-format-file) и [`format()`](#sql-bulk-copy-builder.format) не гарантирована. @@ -760,34 +760,34 @@ interface SqlBulkCopyBuilder { ```js -setServerName(value: string): this; +setServerName(value: string | SecretValue): this; ``` Устанавливает экземпляр SQL Server, к которому устанавливается подключение; [`опция`](https://docs.microsoft.com/ru-ru/sql/tools/bcp-utility#S) *bcp*: *-S*.   ```js -setPort(value: number): this; +setPort(value: number | SecretValue): this; ``` Устанавливает номер порта соединения. По умолчанию: `1433`.   ```js -setUsername(value: string): this; +setUsername(value: string | SecretValue): this; ``` Устанавливает имя пользователя; [`опция`](https://docs.microsoft.com/ru-ru/sql/tools/bcp-utility#U) *bcp*: *-U*.   ```js -setPassword(value: string): this; +setPassword(value: string | SecretValue): this; ``` -Устанавливает имя пользователя; [`опция`](https://docs.microsoft.com/ru-ru/sql/tools/bcp-utility#P) *bcp*: *-P*. +Устанавливает пароль пользователя; [`опция`](https://docs.microsoft.com/ru-ru/sql/tools/bcp-utility#P) *bcp*: *-P*.   ```js -setDatabase(value: string): this; +setDatabase(value: string | SecretValue): this; ``` Устанавливает имя БД, к которой произойдёт подключение; [`опция`](https://docs.microsoft.com/ru-ru/sql/tools/bcp-utility#d) *bcp*: *-d*. diff --git a/API/secrets.md b/API/secrets.md index 4544ead..a88ad2b 100644 --- a/API/secrets.md +++ b/API/secrets.md @@ -78,7 +78,7 @@ toJson(): Object; }, } ``` -Такой JSON-объект можно создать самостоятельно и передавать в методы API скриптов с поддержкой секретов. +Такой JSON-объект можно создать самостоятельно и передавать в методы с поддержкой секретов.   diff --git a/API/winAgent.md b/API/winAgent.md index ec1b431..16681e3 100644 --- a/API/winAgent.md +++ b/API/winAgent.md @@ -13,8 +13,8 @@ WinAgent – сервис создания отчётов MS Word и Excel, ра ## Интерфейс WinAgentBuilder ```js interface WinAgentBuilder { - setCommandUrl(url: string): this; - setDownloadUrl(url: string): this; + setCommandUrl(url: string | SecretValue): this; + setDownloadUrl(url: string | SecretValue): this; auth(): Http.HttpAuth; setConnectTimeout(sec: number): this; setRequestTimeout(sec: number): this; @@ -22,19 +22,19 @@ interface WinAgentBuilder { makeRunMacrosAction(): RunMacroAction; } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для настройки доступа к WinAgent. +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для настройки доступа к WinAgent. Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setCommandUrl(url: string): this; +setCommandUrl(url: string | SecretValue): this; ``` Устанавливает [`URL`](https://ru.wikipedia.org/wiki/URL) агента, на который будут подаваться команды из скрипта. Возвращает `this`.   ```js -setDownloadUrl(url: string): this; +setDownloadUrl(url: string | SecretValue): this; ``` Устанавливает [`URL`](https://ru.wikipedia.org/wiki/URL), по которому можно будет скачивать результирующие документы. Возвращает `this`. From 91df3d04775eb0370a4028028b87d7aee87df163 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Mon, 9 Jun 2025 18:20:19 +0700 Subject: [PATCH 21/37] - Updates for SecretValue - Additional info for creating non-string secrets --- API/pic/secret_json_mode.png | Bin 0 -> 53218 bytes API/scripts.om.d.ts | 3 ++- API/secrets.md | 42 ++++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 API/pic/secret_json_mode.png diff --git a/API/pic/secret_json_mode.png b/API/pic/secret_json_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac2082021ff145b5a88579a6b7e8e2c81be240f GIT binary patch literal 53218 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 ```ts interface Secrets { @@ -54,7 +58,8 @@ getSecret(path: string, key: string): SecretValue; ```ts interface SecretValue { getStorageIdentifier(): string; - getIdentifier(): string; + getPath(): string; + getKey(): string; toJson(): Object; } ``` @@ -62,6 +67,26 @@ interface SecretValue { Опознать методы с поддержкой секретов можно по сигнатуре `setPassword(password: string | SecretValue): this;`. Появляется выбор - использовать простой тип данных или секрет. Проверка типа значения также работает на секретах. Если тип не совпадет с тем, который ожидается методом, вылетит ошибка. +  + +```js +getStorageIdentifier(): string; +``` +Возвращает идентификатор хранилища. + +  + +```js +getPath(): string; +``` +Возвращает название папки, в которой находится секрет. + +  + +```js +getKey(): string; +``` +Возвращает название ключа секрета.   @@ -71,14 +96,15 @@ toJson(): Object; Возвращает JSON-объект секрета вида ```ts { - "type":"OpenBaoKeyValueSecret", + "secret": true, "params": { - "key":"secret-key", - "path":"secret-path", + "key": "secret-key", + "path": "secret-path", + "storageIdentifier": "vault-id", }, } ``` -Такой JSON-объект можно создать самостоятельно и передавать в методы с поддержкой секретов. +Такой JSON-объект также можно передавать в методы с поддержкой секретов. Наличие ключа `secret` определяет объект как секрет вне зависимости от его значения. В текущей реализации для этого ключа необходимо всегда указывать значение `true`, иначе будет выбрасываться ошибка валидации объекта как секрета при передаче его в метод.   @@ -98,11 +124,11 @@ ftp.setHost(secret); Самостоятельное создание JSON-объекта секрета и передача его в метод API скриптов ```ts const secret = { - "type": "OpenBaoKeyValueSecret", + "secret": true, "params": { - "storageIdentifier": "openbao-vault", - "path": "ftp-connection", "key": "host", + "path": "ftp-connection", + "storageIdentifier": "openbao-vault", }, }; From 5fb88f4206a8982d9c1dfb698d3edb23d810df5a Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Mon, 9 Jun 2025 21:27:26 +0700 Subject: [PATCH 22/37] - Updated declarations of methods get*() with secret support --- API/fs.md | 18 +++++++++--------- API/http.md | 34 +++++++++++++++++----------------- API/relationalDB.md | 2 +- API/scripts.om.d.ts | 22 +++++++++++----------- API/secrets.md | 4 ++++ 5 files changed, 42 insertions(+), 38 deletions(-) diff --git a/API/fs.md b/API/fs.md index baaa12e..fff4250 100644 --- a/API/fs.md +++ b/API/fs.md @@ -323,16 +323,16 @@ load(): Filesystem; ```ts interface FTPAdapter extends BaseAdapter { setHost(host: string | SecretValue): this; - getHost(): string; + getHost(): string | SecretValue; setPort(port: number | SecretValue): this; - getPort(): number; + getPort(): number | SecretValue; setUsername(username: string | SecretValue): this; - getUsername(): string | null; + getUsername(): string | SecretValue | null; setPassword(password: string | SecretValue): this; - getPassword(): string | null; + getPassword(): string | SecretValue | null; setRoot(root: string): this; getRoot(): string; @@ -354,7 +354,7 @@ interface FTPAdapter extends BaseAdapter { } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для соединения с сервером [`FTP`](https://ru.wikipedia.org/wiki/FTP). Наследуется от интерфейса [`BaseAdapter`](#base-adapter). Функции для установки параметров подключения поддерживают [секреты](./secrets.md). +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для соединения с сервером [`FTP`](https://ru.wikipedia.org/wiki/FTP). Наследуется от интерфейса [`BaseAdapter`](#base-adapter). Функции поддерживают [секреты](./secrets.md).   @@ -366,7 +366,7 @@ setHost(host: string | SecretValue): this;   ```js -getHost(): string; +getHost(): string | SecretValue; ``` Возвращает адрес хоста. @@ -380,7 +380,7 @@ setPort(port: number | SecretValue): this;   ```js -getPort(): number; +getPort(): number | SecretValue; ``` Возвращает номер порта. @@ -394,7 +394,7 @@ setUsername(username: string | SecretValue): this;   ```js -getUsername(): string | null; +getUsername(): string | SecretValue | null; ``` Возвращает имя пользователя. @@ -408,7 +408,7 @@ setPassword(password: string | SecretValue): this;   ```js -getPassword(): string | null; +getPassword(): string | SecretValue | null; ``` Возвращает пароль. diff --git a/API/http.md b/API/http.md index 2c800f1..9a79c32 100644 --- a/API/http.md +++ b/API/http.md @@ -278,30 +278,30 @@ interface Url { getUrl(): string; setUrlPath(path: string | SecretValue): boolean; - getUrlPath(): string; + getUrlPath(): string | SecretValue; setUrlScheme(scheme: string | SecretValue): boolean; - getUrlScheme(): string; + getUrlScheme(): string | SecretValue; setHost(host: string | SecretValue): boolean; - getHost(): string; + getHost(): string | SecretValue; setPort(port: number | string | SecretValue): boolean; - getPort(): number | null; + getPort(): number | SecretValue | null; setUser(user: string | SecretValue): boolean; - getUser(): string | null; + getUser(): string | SecretValue | null; setPassword(password: string | SecretValue): boolean; - getPassword(): string | null; + getPassword(): string | SecretValue | null; setFragment(fragment: string | SecretValue): boolean; - getFragment(): string | null; + getFragment(): string | SecretValue | null; params(): UrlParams; } ``` -Интерфейс построения [`URL`](https://ru.wikipedia.org/wiki/URL). Функции для установки параметров подключения поддерживают [секреты](./secrets.md). +Интерфейс построения [`URL`](https://ru.wikipedia.org/wiki/URL). Функции поддерживают [секреты](./secrets.md).   @@ -315,7 +315,7 @@ setUrl(url: string | SecretValue): boolean; ```js getUrl(): string; ``` -Возвращает URL. +Возвращает URL или выбрасывает исключение `This interface call was denied for JS context`, если хотя бы одна из частей URL является секретом.   @@ -327,7 +327,7 @@ setUrlPath(path: string | SecretValue): boolean;   ```js -getUrlPath(): string; +getUrlPath(): string | SecretValue; ``` Возвращает путь на сервере. @@ -341,7 +341,7 @@ setUrlScheme(scheme: string | SecretValue): boolean;   ```js -getUrlScheme(): string; +getUrlScheme(): string | SecretValue; ``` Возвращает схему URL (протокол). @@ -355,7 +355,7 @@ setHost(host: string | SecretValue): boolean;   ```js -getHost(): string; +getHost(): string | SecretValue; ``` Возвращает имя или адрес хоста, установленное в URL. @@ -369,7 +369,7 @@ setPort(port: number | string | SecretValue): boolean;   ```js -getPort(): number | null; +getPort(): number | SecretValue | null; ``` Возвращает номер порта или `null`, если он не установлен. @@ -383,7 +383,7 @@ setUser(user: string | SecretValue): boolean;   ```js -getUser(): string | null; +getUser(): string | SecretValue | null; ``` Возвращает имя пользователя или `null`, если оно не установлено. @@ -397,7 +397,7 @@ setPassword(password: string | SecretValue): boolean;   ```js -getPassword(): string | null; +getPassword(): string | SecretValue | null; ``` Возвращает пароль или `null`, если он не установлен. @@ -411,7 +411,7 @@ setFragment(fragment: string | SecretValue): boolean;   ```js -getFragment(): string | null; +getFragment(): string | SecretValue | null; ``` Возвращает идентификатор якоря или `null`, если он не установлен. @@ -526,7 +526,7 @@ interface HttpAuth { setStatus(status: boolean): this; } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для настроек аутентификации HTTP. Все функции возвращают `this`. Функции для установки параметров подключения поддерживают [секреты](./secrets.md). +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), для настроек аутентификации HTTP. Все функции возвращают `this`. Функции поддерживают [секреты](./secrets.md).   diff --git a/API/relationalDB.md b/API/relationalDB.md index e444cc3..eedbbe4 100644 --- a/API/relationalDB.md +++ b/API/relationalDB.md @@ -331,7 +331,7 @@ interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { setProtocol(protocol: string): this; } ``` -[`Коннектор`](../appendix/glossary.md#connector) для подключения к базе данных [`Snowflake`](https://en.wikipedia.org/wiki/Snowflake_Inc%2E) (для подключения используется [PHP PDO Driver](https://docs.snowflake.com/en/user-guide/php-pdo-driver.html)). Все функции возвращают `this`. Интерфейс наследуется от [`SqlConnectorBuilder`](#sql-connector-builder). Функции для установки параметров подключения поддерживают [секреты](./secrets.md). +[`Коннектор`](../appendix/glossary.md#connector) для подключения к базе данных [`Snowflake`](https://en.wikipedia.org/wiki/Snowflake_Inc%2E) (для подключения используется [PHP PDO Driver](https://docs.snowflake.com/en/user-guide/php-pdo-driver.html)). Все функции возвращают `this`. Интерфейс наследуется от [`SqlConnectorBuilder`](#sql-connector-builder). Функции поддерживают [секреты](./secrets.md).   diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 0afa423..0894bc8 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -873,16 +873,16 @@ export interface BaseAdapter { export interface FTPAdapter extends BaseAdapter { setHost(host: string | SecretValue): this; - getHost(): string; + getHost(): string | SecretValue; setPort(port: number | SecretValue): this; - getPort(): number; + getPort(): number | SecretValue; setUsername(username: string | SecretValue): this; - getUsername(): string | null; + getUsername(): string | SecretValue | null; setPassword(password: string | SecretValue): this; - getPassword(): string | null; + getPassword(): string | SecretValue | null; setRoot(root: string): this; getRoot(): string; @@ -1395,25 +1395,25 @@ export namespace Http { getUrl(): string; setUrlPath(path: string | SecretValue): boolean; - getUrlPath(): string; + getUrlPath(): string | SecretValue; setUrlScheme(scheme: string | SecretValue): boolean; - getUrlScheme(): string; + getUrlScheme(): string | SecretValue; setHost(host: string | SecretValue): boolean; - getHost(): string; + getHost(): string | SecretValue; setPort(port: number | string | SecretValue): boolean; - getPort(): number | null; + getPort(): number | SecretValue | null; setUser(user: string | SecretValue): boolean; - getUser(): string | null; + getUser(): string | SecretValue | null; setPassword(password: string | SecretValue): boolean; - getPassword(): string | null; + getPassword(): string | SecretValue | null; setFragment(fragment: string | SecretValue): boolean; - getFragment(): string | null; + getFragment(): string | SecretValue | null; params(): UrlParams; } diff --git a/API/secrets.md b/API/secrets.md index 2695d5e..b5fbf2a 100644 --- a/API/secrets.md +++ b/API/secrets.md @@ -5,10 +5,14 @@ Такие данные называются **секретами**. Они бывают разных видов. Поддерживаемые типы: - `OpenBaoKeyValueSecret` - ключ-значение +  + **Важно!** Запись в хранилище данных в формате, отличном от строкового (например порт подключения является числом), необходимо выполнять в режиме JSON, иначе они будут трактоваться как строка. ![Режим JSON в хранилище секретов](./pic/secret_json_mode.png) +  + ## Интерфейс Secrets ```ts interface Secrets { From 1c3fe763bcebec87116a615d6d545e6224e3056d Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Tue, 10 Jun 2025 14:08:51 +0700 Subject: [PATCH 23/37] Corrected small mistakes --- API/common.md | 4 ++-- API/readingGrid.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/API/common.md b/API/common.md index 82d87b4..a689923 100644 --- a/API/common.md +++ b/API/common.md @@ -400,7 +400,7 @@ recalculate(): boolean; ```js backup(path?: string): EntityInfo | boolean; ``` -Сохраняет резервную копию в логах модели: в интерфейсе Optimacros на вкладке `Центр безопастности`->`Логи`->`Резервные копии`. Если указан путь `path`, после создания копии вызовется функция `export()` и вернётся её результат типа `boolean`. Если `path` не указан, возвращает сущность резервной копии в виде [`EntityInfo`](./common.md#entity-info). +Сохраняет резервную копию в логах модели: в интерфейсе Optimacros на вкладке `Центр безопастности`->`Логи`->`Резервные копии`. Если указан путь `path`, после создания копии вызовется функция `export()` и вернётся её результат типа `boolean`. Если `path` не указан, возвращает сущность резервной копии в виде [`EntityInfo`](#entity-info).   @@ -690,7 +690,7 @@ name(): string; ```js code(): string; ``` -Возвращает код сущности. В Optimacros всего две сущности могут иметь код: элементы справочников и кубы. +Возвращает код сущности.   diff --git a/API/readingGrid.md b/API/readingGrid.md index a3bdb13..d120188 100644 --- a/API/readingGrid.md +++ b/API/readingGrid.md @@ -222,7 +222,7 @@ interface Cell { ```js setValue(value: number | string | boolean | null): this; ``` -Устанавливает значение клетки. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. В случае клетки формата справочника в качестве значени можно использовать [имя элемента](./common.md#name), его [код](./common.md#code), [`longId`](./common.md#long-id) или [пару `отображаемое-имя||имя`](cell.get-context-value). Возвращает `this`. +Устанавливает значение клетки. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. В случае клетки формата справочника в качестве значения можно использовать [имя элемента](./common.md#name), его [код](./common.md#code), [`longId`](./common.md#long-id) или [пару `отображаемое-имя||имя`](cell.get-context-value). Возвращает `this`.   From bc17a1a02327f56c476d21ae15af2c84a103100e Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Tue, 10 Jun 2025 14:51:11 +0700 Subject: [PATCH 24/37] - Updated the main chapter about secrets --- API/secrets.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/API/secrets.md b/API/secrets.md index b5fbf2a..b05658a 100644 --- a/API/secrets.md +++ b/API/secrets.md @@ -1,11 +1,8 @@ # Секреты -Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом [OpenBao](https://openbao.org/), который поставляется с дистрибутивом. У сервиса есть UI: `workspace_url/openbao/ui`. Через него клиенты записывают в защищенное хранилище важные данные, чтобы затем использовать в скриптах. +Для хранения чувствительных данных (логины, пароли, хеши) пользователи могут воспользоваться сервисом защищённого хранилища секретов. Поддерживаются решения [OpenBao](https://openbao.org/) и [Hashicorp Vault](https://www.vaultproject.io/). OpenBao поставляется с дистрибутивом. У сервиса есть UI: `workspace_url/openbao/ui`. К хранилищу Hashicorp Vault можно настроить удаленный доступ. К воркспейсу могут быть подключены сразу несколько хранилищ. Доступ настраивается в манифесте воркспейса администратором. -Такие данные называются **секретами**. Они бывают разных видов. Поддерживаемые типы: -- `OpenBaoKeyValueSecret` - ключ-значение - -  +Клиенты записывают в защищённое хранилище важные данные, чтобы затем использовать в скриптах. Такие данные называются **секретами**. Они бывают разных видов. В данный момент приложением поддерживается один тип - `ключ-значение`. **Важно!** Запись в хранилище данных в формате, отличном от строкового (например порт подключения является числом), необходимо выполнять в режиме JSON, иначе они будут трактоваться как строка. @@ -28,7 +25,7 @@ getStorage(vaultId: string): SecretStorage; ``` Возвращает хранилище [`SecretStorage`](#secret-storage) с идентификатором `vaultId`. -К воркспейсу могут быть подключены сразу несколько хранилищ. Доступ настраивается в манифесте воркспейса администратором. Поддерживаются решения [OpenBao](https://openbao.org/) и [Hashicorp Vault](https://www.vaultproject.io/). Список подключенных хранилищ и их идентификаторы `id` можно посмотреть в панели администратора воркспейса в разделе `Secrets`. В одном скрипте можно обращаться к любому из них или сразу к нескольким. +Список подключенных хранилищ и их идентификаторы `id` можно посмотреть в панели администратора воркспейса в разделе `Secrets`. В одном скрипте можно обращаться к любому из них или сразу к нескольким.   @@ -38,7 +35,7 @@ interface SecretStorage { getSecret(path: string, key: string): SecretValue; } ``` -Интерфейс для работы с защищенным хранилищем секретов. +Интерфейс для работы с защищённым хранилищем секретов.   From 0171d9e12c447b24605fbfbca1696aa7c33709b5 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Tue, 10 Jun 2025 16:11:45 +0700 Subject: [PATCH 25/37] Corrections after review --- API/common.md | 6 +-- API/readingGrid.md | 105 ++++++++++++++++++++-------------------- appendix/constraints.md | 4 +- 3 files changed, 57 insertions(+), 58 deletions(-) diff --git a/API/common.md b/API/common.md index a689923..883edd6 100644 --- a/API/common.md +++ b/API/common.md @@ -587,7 +587,7 @@ interface UserInfo { ```js getEntity(): EntityInfo; ``` -Возвращает сущность пользователя в виде [`EntityInfo`](./common.md#entity-info). +Возвращает сущность пользователя в виде [`EntityInfo`](#entity-info).   @@ -700,7 +700,7 @@ alias(): string; ``` Возвращает отображаемое имя. -Если `this` является сущностью элемента справочника, в настройках которого задано некоторое свойство в качестве отображаемого имени (опция `Отображение`), и для этой сущности задано значение этого свойства, то возвращает значение этого свойства. +Если `this` является сущностью элемента справочника, в настройках которого задано некоторое свойство в качестве отображаемого имени (колонка `Отображаемое имя` на вкладке `Справочники`), и для этой сущности задано значение этого свойства, то возвращает значение этого свойства. Иначе возвращает [`name()`](#name). @@ -736,7 +736,7 @@ interface EntitiesInfo { getCollection(longId: number[]): EntityInfo[]; } ``` -Интерфейс для получения сущности по [`longId`](./common.md#long-id). +Интерфейс для получения сущности по [`longId`](#long-id).   diff --git a/API/readingGrid.md b/API/readingGrid.md index d120188..84802a8 100644 --- a/API/readingGrid.md +++ b/API/readingGrid.md @@ -3,9 +3,9 @@ ## Интерфейс GridRangeChunk ```ts interface GridRangeChunk { - cells(): Cells; - rows(): Labels; - columns(): Labels; + cells(): Cells; + rows(): Labels; + columns(): Labels; } ``` Интерфейс для обработки части строк [`GridRange`](./views.md#grid-range) — чанка. Содержит информацию о заголовках (возможно многоуровневых) и ячейках двумерной таблицы. @@ -36,12 +36,12 @@ columns(): Labels; ### Интерфейс Labels ```ts interface Labels { - start(): number; - count(): number; - all(): LabelsGroup[]; - get(index: number): LabelsGroup | null; - chunkInstance(): GridRangeChunk; - findLabelByLongId(longId: number): Label | null; + start(): number; + count(): number; + all(): LabelsGroup[]; + get(index: number): LabelsGroup | null; + chunkInstance(): GridRangeChunk; + findLabelByLongId(longId: number): Label | null; } ``` Интерфейс, представляющий набор объектов [`LabelsGroup`](#labels-group), то есть набор заголовков строк/столбцов с их возможно многоуровневой структурой. Как правило, его можно получить функциями интерфейса [`GridRangeChunk`](#grid-range-chunk). @@ -97,9 +97,9 @@ findLabelByLongId(longId: number): Label | null; ## Интерфейс LabelsGroup ```ts interface LabelsGroup { - all(): Label[]; - first(): Label; - cells(): Cells; + all(): Label[]; + first(): Label; + cells(): Cells; } ``` Интерфейс, представляющий многоуровневый набор заголовков конкретной строки или столбца. @@ -138,12 +138,12 @@ interface Label = EntityInfo; ### Интерфейс Cells ```ts interface Cells { - all(): Cell[]; - first(): Cell | null; - setValue(value: number | string | boolean | null): this; - count(): number; - chunkInstance(): GridRangeChunk; - getByIndexes(indexes: number[]): Cells; + all(): Cell[]; + first(): Cell | null; + setValue(value: number | string | boolean | null): this; + count(): number; + chunkInstance(): GridRangeChunk; + getByIndexes(indexes: number[]): Cells; } ``` Интерфейс, представляющий (как правило, прямоугольный) набор клеток таблицы. @@ -197,21 +197,21 @@ getByIndexes(indexes: number[]): Cells; ### Интерфейс Cell ```ts interface Cell { - setValue(value: number | string | boolean | null): this; - - getValue(): number | string | null; - getVisualValue(): string | null; - getNativeValue(): number | string | null; - getContextValue(): string | null; - - definitions(): number[]; - columns(): LabelsGroup | null; - rows(): LabelsGroup | null; - - dropDown(): Labels; - dropDownSelector(): DropDownSelector; - getFormatType(): string; - isEditable(): boolean; + setValue(value: number | string | boolean | null): this; + + getValue(): number | string | null; + getVisualValue(): string | null; + getNativeValue(): number | string | null; + getContextValue(): string | null; + + definitions(): number[]; + columns(): LabelsGroup | null; + rows(): LabelsGroup | null; + + dropDown(): Labels; + dropDownSelector(): DropDownSelector; + getFormatType(): string; + isEditable(): boolean; } ``` Интерфейс, представляющий клетку таблицы. @@ -318,33 +318,33 @@ isEditable(): boolean; ### Интерфейс DropDownSelector -```js +```ts interface DropDownSelector { - totalCount(): number; - generator(chunkSize: number | null): IterableIterator; + totalCount(): number; + generator(chunkSize: number | null): IterableIterator; } ``` -Интерфейс постраничного получения опций выпадающего списка для клеток формата сущности — `'ENTITY'`, `'TIME_ENTITY'`, `'VERSION'`. Который должен во всех случаях совпадать со списком, доступным пользователю через `web`-интерфейс (со всеми применимыми фильтрациями). +Интерфейс постраничного получения опций выпадающего списка для клеток формата сущности — `'ENTITY'`, `'TIME_ENTITY'`, `'VERSION'`, который должен во всех случаях совпадать со списком, доступным пользователю через `web`-интерфейс (со всеми применимыми фильтрациями). -Для клеток, доступных только для чтения, список опций всё равно доступен, хотя изменение значения клетки невозможно, поэтому чтобы понять, можно ли изменять клетку, стоит обратиться к методу [`Cell.isEditable()`](#cell.is-editable). Если недоступно даже чтение значения клетки, попытка получения данного интерфейса приведёт к ошибке. +Для клеток, доступных только для чтения, список опций всё равно доступен, хотя изменение значения клетки невозможно. Чтобы понять, можно ли изменять клетку, стоит обратиться к методу [`Cell.isEditable()`](#cell.is-editable). Если недоступно даже чтение значения клетки, попытка получения данного интерфейса приведёт к ошибке. -По неизвестной науке причине с помощью этого интерфейса также **возможно** чтение списка доспупных пользовательских измерений мультикуба `User Lists`. По той же причине, если в справочнике типа `Cube Link` не установлено значение мультикуба `Multicube Link`, то у клетки пропадает выпадающий список полностью и она становится нередактируемой, а значит, попытка чтения опций кубов `Cube Link` приведёт к ошибке. Эта же причниа влияет и на то, что если в справочнике создать свойство с форматом этого же или родительского справочника и применить зависимый контекст по измерению, то в `web`-интерфейсе фильтрация **не** будет работать, но интерфейс `DropDownSelector` **будет** работать с фильтрацией. +По неизвестной науке причине с помощью этого интерфейса также **возможно** чтение списка доступных пользовательских измерений мультикуба в колонке `User Lists` на вкладке `Multicubes`. По той же причине, если в справочнике типа `Cube Link` не установлено значение мультикуба в колонке `Multicube Link`, то у клетки пропадает выпадающий список полностью и она становится нередактируемой, а значит, попытка чтения опций кубов в колонке `Cube Link` приведёт к ошибке. Эта же причина влияет и на то, что если в справочнике создать свойство с форматом этого же или родительского справочника и применить зависимый контекст по измерению, то в `web`-интерфейсе фильтрация **не будет** работать, но интерфейс `DropDownSelector` **будет** работать с фильтрацией. -Для получения новыйх страниц требуется блокировка того же уровня, что и для получения ссылки на сам интерфейс с помощью [`Cell.dropDownSelector`](#cell.dropdown-selector). +Для получения новых страниц требуется блокировка того же уровня, что и для получения ссылки на сам интерфейс с помощью [`Cell.dropDownSelector`](#cell.dropdown-selector). -Также стоит отметить, что наличе опции в выпадающем списке не гарантирует, что данное значение может быть установлено. +Так же стоит отметить, что наличие опции в выпадающем списке не гарантирует, что данное значение может быть установлено.   -```js +```ts totalCount(): number; ``` Возвращает общее количество опций выпадающего списка.   -```js +```ts generator(chunkSize: number | null): IterableIterator; ``` Метод получения итератора для постраничного чтения опций выпадающего списка. Аргумент `chunkSize` — максимальное количество опций на одной странице итератора в интервале от 500 до 5000 (по умолчанию 1000). Влияние параметра `chunkSize` на скорость работы итератора достаточно не изучено и это предстоит устанавливать в каждом конкретном случае. Возвращает итерируемый объект для чтения страниц опций выдающего списка [`DropDownSelectorChunk`](#dropdown-selector-chunk). @@ -352,33 +352,32 @@ generator(chunkSize: number | null): IterableIterator;   ### Интерфейс DropDownSelectorChunk - -```js +```ts interface DropDownSelectorChunk { - start(): number; - count(): number; - all(): Label[]; + start(): number; + count(): number; + all(): Label[]; } ``` -Интерфейс содержащий одну страницу опций выпадающего списка возможных значений клетки. +Интерфейс, содержащий одну страницу опций выпадающего списка возможных значений клетки.   -```js +```ts start(): number; ``` Возвращает номер первой опции текущей страницы выдающего списка, начиная отсчёт с 0.   -```js +```ts count(): number; ``` -Возвращает общее число опций на данной странице. +Возвращает общее число опций на текущей странице.   -```js +```ts all(): Label[]; ``` Возвращает список сущностей [Label](#label) опций выпадающего списка. diff --git a/appendix/constraints.md b/appendix/constraints.md index c791b0d..f468439 100644 --- a/appendix/constraints.md +++ b/appendix/constraints.md @@ -13,7 +13,7 @@ Если в сводной таблице в строках или столбцах нет измерений, система скриптов создаёт виртуальное измерение, к которому можно получить доступ стандартным способом, и вызов ```js -definitionInfo.getColumnDimensions()[0]getDimensionEntity().name(); +definitionInfo.getColumnDimensions()[0].getDimensionEntity().name(); ``` вернёт специальное значение `'Empty 1 0'`. @@ -22,7 +22,7 @@ definitionInfo.getColumnDimensions()[0]getDimensionEntity().name(); Такое измерение не содержит заголовков и вызов [`LabelsGroup`](../API/readingGrid.md#labels-group).`all()` вернёт пустой массив. -Если таблица не собержит ни строк, ни колонок, то доступ к единственной ячейке возможет только с помощью метода [`GridRangeChunk`](../API/readingGrid.md#grid-range-chunk).`cells()`. +Если таблица не содержит ни строк, ни колонок, то доступ к единственной ячейке возможет только с помощью метода [`GridRangeChunk`](../API/readingGrid.md#grid-range-chunk).`cells()`.   From 897bc6dfc349eb212bc4562546928ba533af0dc0 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Tue, 10 Jun 2025 16:16:10 +0700 Subject: [PATCH 26/37] Code must be in TS style --- API/readingGrid.md | 62 +++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/API/readingGrid.md b/API/readingGrid.md index 84802a8..7b3586e 100644 --- a/API/readingGrid.md +++ b/API/readingGrid.md @@ -12,21 +12,21 @@ interface GridRangeChunk {   -```js +```ts cells(): Cells; ``` Возвращает ссылку на набор ячеек [`Cells`](#cells) текущего чанка.   -```js +```ts rows(): Labels; ``` Возвращает интерфейс [`Labels`](#labels), представляющий заголовки строк.   -```js +```ts columns(): Labels; ``` Возвращает интерфейс [`Labels`](#labels), представляющий заголовки столбцов. @@ -48,14 +48,14 @@ interface Labels {   -```js +```ts start(): number; ``` Возвращает номер первой строки/столбца текущего [`GridRangeChunk`](#grid-range-chunk) в таблице [`Grid`](./views.md#grid).   -```js +```ts count(): number; ``` Возвращает количество строк/столбцов в наборе. @@ -66,28 +66,28 @@ count(): number;   -```js +```ts all(): LabelsGroup[]; ``` Возвращает массив объектов заголовков каждой строки/столбца [`LabelsGroup`](#labels-group).   -```js +```ts get(index: number): LabelsGroup | null; ``` Аналог `all()[index]`. В случае некорректного индекса возвращает `null`.   -```js +```ts chunkInstance(): GridRangeChunk; ``` Возвращает обратную ссылку на [`GridRangeChunk`](#grid-range-chunk), из которого был получен `this`.   -```js +```ts findLabelByLongId(longId: number): Label | null; ``` Возвращает объект [`Label`](#label) по его [`longId`](./common.md#long-id), если он присутствует в `this`, иначе — `null`. @@ -106,21 +106,21 @@ interface LabelsGroup {   -```js +```ts all(): Label[]; ``` Возвращает массив конкретных заголовков [`Label`](#label).   -```js +```ts first(): Label; ``` Аналог `all()[0]`.   -```js +```ts cells(): Cells; ``` Возвращает интерфейс [`Cells`](#cells), предоставляющий доступ к ячейкам данной строки или столбца. @@ -150,14 +150,14 @@ interface Cells {   -```js +```ts all(): Cell[]; ``` Возвращает одномерный массив всех клеток [`Cell`](#cell).   -```js +```ts first(): Cell | null; ``` Аналог `all()[0]`. Возвращает `null`, если массив клеток пустой. @@ -165,14 +165,14 @@ first(): Cell | null;   -```js +```ts setValue(value: number | string | boolean | null): this; ``` Устанавливает одно и то же значение для всех клеток. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых от них клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. Возвращает `this`.   -```js +```ts count(): number; ``` Возвращает количество клеток в наборе. @@ -180,14 +180,14 @@ count(): number;   -```js +```ts chunkInstance(): GridRangeChunk; ``` Возвращает обратную ссылку на [`GridRangeChunk`](#grid-range-chunk), из которого был получен `this`.   -```js +```ts getByIndexes(indexes: number[]): Cells; ``` Производит выборку из одномерного представления клеток объекта `this` по индексам `indexes` и возвращает новый объект [`Cells`](#cells). В этом случае функция [`chunkInstance()`](#chunk-instance) для нового объекта будет возвращать ссылку на тот же самый объект [`GridRangeChunk`](#grid-range-chunk), что и для `this`. Это *единственный* способ создать непрямоугольный объект [`Cells`](#cells). @@ -219,7 +219,7 @@ interface Cell {   -```js +```ts setValue(value: number | string | boolean | null): this; ``` Устанавливает значение клетки. Отрабатывает в момент вызова и мгновенно приводит к пересчёту зависимых клеток. Поэтому ***не*** рекомендуется к использованию в больших мультикубах. В случае клетки формата справочника в качестве значения можно использовать [имя элемента](./common.md#name), его [код](./common.md#code), [`longId`](./common.md#long-id) или [пару `отображаемое-имя||имя`](cell.get-context-value). Возвращает `this`. @@ -227,14 +227,14 @@ setValue(value: number | string | boolean | null): this;   -```js +```ts getValue(): number | string | null; ``` Возвращает значение клетки, которое видит пользователь. Если клетка имеет логический формат, то возвращается строковое значение `'true'` или `'false'`.   -```js +```ts getVisualValue(): string | null; ``` Возвращает отображаемое значение в ячейке, если куб в формате даты или справочника, для других форматов куба возвращает `null`. @@ -242,7 +242,7 @@ getVisualValue(): string | null;   -```js +```ts getNativeValue(): number | string | null; ``` Возвращает самородное значение клетки, зависящее от формата. Если клетка имеет формат справочника, то возвращается [`longId`](./common.md#long-id). @@ -252,7 +252,7 @@ getNativeValue(): number | string | null;   -```js +```ts getContextValue(): string | null; ``` Если ячейка имеет формат справочника, в настройках которого задано некоторое свойство `prop` в качестве отображаемого имени (опция `Отображение`), и для этой ячейки задано значение этого свойства, то возвращает строку, состоящую из имени, двойной вертикальной черты и значения свойства `prop`. Например, для отображамого имени `Берлин` и имени элемента `#5` — `'Берлин||#5'`. @@ -261,21 +261,21 @@ getContextValue(): string | null;   -```js +```ts definitions(): number[]; ``` То же, что и [`CubeCell.definitions()`](./cubeCell.md#cube-cell.definitions).   -```js +```ts columns(): LabelsGroup | null; ``` Возвращает многоуровневый набор заголовков [`LabelsGroup`](#labels-group) конкретного столбца, или `null`, если у клетки нет измерений на столбцах.   -```js +```ts rows(): LabelsGroup | null; ``` Возвращает многоуровневый набор заголовков [`LabelsGroup`](#labels-group) конкретной строки, или `null`, если у клетки нет измерений на строках. @@ -283,7 +283,7 @@ rows(): LabelsGroup | null;   -```js +```ts dropDown(): Labels; ``` Этот метод признан устаревшим. Вместо него стоит использовать метод [`dropDownSelector()`](#cell.dropdown-selector). @@ -293,14 +293,14 @@ dropDown(): Labels;   -```js +```ts dropDownSelector(): DropDownSelector; ``` Позволяет постранично читать набор опций выпадающего списка значений клетки. Требует наличия `SHARED` блокировки для всех случаев, кроме колонки `Api Service Model` [таблицы веб-сервисов воркспейса](./apiServicesAdministration.md), которая требует отсутствия блокировок — `UNLOCK` (чтение опций клеток колонки `Api Service Script` требует `SHARED` блокировки, так как список скриптов без чтения модели получить не выйдет). Вызов на клетке, не содержащей выпадающего списка, приведёт к ошибке. Возвращает ссылку на интерфейс [`DropDownSelector`](#dropdown-selector) выпадающего списка, который в интерфейсе пользователя `Optimacros` можно получить кликом по треугольнику внутри ячейки.   -```js +```ts getFormatType(): string; ``` Возвращает строку с форматом клетки. Возможные значения: `'NUMBER'`, `'BOOLEAN'`, @@ -309,7 +309,7 @@ getFormatType(): string;   -```js +```ts isEditable(): boolean; ``` Возвращает признак возможности редактирования ячейки пользователем. @@ -333,7 +333,7 @@ interface DropDownSelector { Для получения новых страниц требуется блокировка того же уровня, что и для получения ссылки на сам интерфейс с помощью [`Cell.dropDownSelector`](#cell.dropdown-selector). -Так же стоит отметить, что наличие опции в выпадающем списке не гарантирует, что данное значение может быть установлено. +Также стоит отметить, что наличие опции в выпадающем списке не гарантирует, что данное значение может быть установлено.   From 93d559aef45b5f0bcb6a40db7c47044c5f0041d6 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Thu, 19 Jun 2025 16:51:51 +0700 Subject: [PATCH 27/37] - Updated interface Http.Params --- API/http.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/http.md b/API/http.md index 9a79c32..ef52816 100644 --- a/API/http.md +++ b/API/http.md @@ -67,7 +67,7 @@ interface Params { clear(): boolean; } ``` -Интерфейс, представляющий набор параметров и их значений. +Интерфейс, представляющий набор параметров и их значений. Позволяет устанавливать значение [`SecretValue`](./secrets.md#secret-value). Секрет передаётся по сети в виде JSON строки, **не раскрывая** секретное значение.   From 76c47af110f96812deb8927e48739d7aea7ded44 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Thu, 19 Jun 2025 22:56:03 +0700 Subject: [PATCH 28/37] Small corrections after review --- API/common.md | 2 +- changelog.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/API/common.md b/API/common.md index e058088..ec9f705 100644 --- a/API/common.md +++ b/API/common.md @@ -751,7 +751,7 @@ get(longId: number): EntityInfo | null; ```js getCollection(longId: number[]): (EntityInfo | null)[]; ``` -Возвращает массив сущностей [`EntityInfo`](#entity-info) параллельный массиву их [`longId`](#long-id). Если сущность не найдена, на её месте будет возвращён `null`. +Возвращает массив сущностей [`EntityInfo`](#entity-info), параллельный массиву их [`longId`](#long-id). Если сущность не найдена, на её месте будет возвращёно значение `null`.   diff --git a/changelog.md b/changelog.md index c73930e..5e9da48 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,7 @@ | Дата релиза | Тег релиза | Версия ScriptAPI | Версии MiddleWork | Версия приложения | Изменения | | --- | --- | --- | --- | --- | --- | -| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Интерфейсы доступа к содержимому грида — заголовкам и ячейкам — были вынесены в отдельный файл — [readingGrid.md](./API/readingGrid.md)
  • Был **обратно несовместимо** переработан метод [`EntitiesInfo.getCollection()`](./API/common.md#entities-info.get-collection)
  • Добавлен интерфейс постраничного получения списка опций значений клетки [DropDownSelector](./API/readingGrid.md#dropdown-selector) и метод для получения доступа к нему — [`Cell.dropDownSelector()`](./API/readingGrid.md#cell.dropdown-selector)
  • Метод получения списка опций значений клетки [`Cell.dropDown()`](./API/readingGrid.md#cell.dropdown) признан устаревшим
| +| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Интерфейсы доступа к содержимому грида — заголовкам и ячейкам — вынесены в отдельный файл [readingGrid.md](./API/readingGrid.md)
  • **Обратно несовместимо** переработан метод [`EntitiesInfo.getCollection()`](./API/common.md#entities-info.get-collection)
  • Добавлен интерфейс постраничного получения списка опций значений клетки [DropDownSelector](./API/readingGrid.md#dropdown-selector) и метод для получения доступа к нему — [`Cell.dropDownSelector()`](./API/readingGrid.md#cell.dropdown-selector)
  • Метод получения списка опций значений клетки [`Cell.dropDown()`](./API/readingGrid.md#cell.dropdown) признан устаревшим
| | 24.03.2025 | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейсе [Filesystem](./API/fs.md#filesystem) изменены декларации функций `delete()`, `rename()`, `copy()`, `createDir()`, `deleteDir()`, `getSize()`
  • В интерфейс [CellBuffer](./API/common.md#cell-buffer) добавлена функция `lastApplyErrors()`
  • В интерфейсе [Importer](./API/exportImport.md#importer) исправлены исключения, которые могут бросать функции
| | 16.12.2024 | [v9.?00.x.x](https://github.com/optimacros/scripts_documentation/tree/v9.?00.x.x) | — |
  • 9.?00.x.x
|
|
  • Интерфейс работы с лицензиями воркспейса `EnterpriseLicenseManager` заменён на новый интерфейс работы с данными договора о параметрах воркспейса [EnterpriseContractManager](./API/common.md#enterprise-contract-manager)
| | 15.11.2024 | [9.200.x.13](https://github.com/optimacros/scripts_documentation/tree/v9.200.x.13) | — |
  • 9.200.dev.13
|
  • 9.200.x.x
|
  • В интерфейс [Filesystem](./API/fs.md#filesystem) добавлен метод для изменения кодировки файла `changeTextFileCharset()`
| From e8f47ee5f905724eeabd9ee494ca74469eefedcd Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Mon, 14 Jul 2025 14:39:19 +0700 Subject: [PATCH 29/37] Some corrections after review --- API/common.md | 8 ++++---- API/scriptChains.md | 4 ++-- appendix/glossary.md | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/API/common.md b/API/common.md index e6a601e..e86c5d3 100644 --- a/API/common.md +++ b/API/common.md @@ -75,7 +75,7 @@ copyData(): CopyData; ```js apiServiceRequestInfo(): ApiService.RequestInfo | null; ``` -Возвращает ссылку на интерфейс [`ApiService.RequestInfo`](./apiService.md#request-info), если скрипт вызван через API Service, или `null` иначе. +Возвращает ссылку на интерфейс [`ApiService.RequestInfo`](./apiService.md#request-info), если скрипт вызван через API Service, или `null` иначе.   @@ -250,21 +250,21 @@ setStatusMessage(message: string): this; ```js getRequestId(): string | null; ``` -Каждый запуск скрипта должен существовать в рамках пользовательского или системного [запроса](../appendix/glossary.md#request). Метод предполагает возможность вернуть `null`, но такое поведение можно смело считать багом системы запуска скриптов. Возвращает идентификатор [запроса](../appendix/glossary.md#request), по которому можно найти запись о запуске скрипта в истории запуска скриптов в `web`-интерфейсе (`Macros` -> `Scripts` -> `Launch History`) и в панели администратора `Requests` -> `History`, если запрос на запуск скрипта был сделан пользователем. Скрипты, запущенные по расписанию или через систему API сервисов считаются системными. +Каждый запуск скрипта должен существовать в рамках пользовательского или системного [запроса](../appendix/glossary.md#request). Метод возвращает идентификатор текущего запроса. Предполагает возможность вернуть `null`, но такое поведение можно смело считать багом системы запуска скриптов. По идентификатору можно найти запись о запуске скрипта в истории запуска скриптов в web-интерфейсе на вкладке `Macros` -> `Scripts` -> `Launch History` и в панели администратора на вкладке `Requests` -> `History`, если запрос на запуск скрипта был сделан пользователем. Скрипты, запущенные по расписанию или через систему API сервисов считаются системными.   ```js getScriptName(): string | null; ``` -Возвращает имя сущности текущего исполняемого скрипта кроме случая запуска сниппета кода с помощью метода [`ResultActionsInfo.makeCodeExecutionAction()`](./scriptChains.md#make-code-execution-action) — тогда вернёт `null`. +Возвращает имя сущности текущего исполняемого скрипта. В случае запуска сниппета кода с помощью метода [`ResultActionsInfo.makeCodeExecutionAction()`](./scriptChains.md#make-code-execution-action) возвращает `null`.   ```js getScriptLongId(): string | null; ``` -Возвращает [long id](#long-id) сущности текущего исполняемого скрипта кроме случая запуска сниппета кода с помощью метода [`ResultActionsInfo.makeCodeExecutionAction()`](./scriptChains.md#make-code-execution-action) — тогда вернёт `null`. +Возвращает [`longId`](./views.md#long-id) сущности текущего исполняемого скрипта. В случае запуска сниппета кода с помощью метода [`ResultActionsInfo.makeCodeExecutionAction()`](./scriptChains.md#make-code-execution-action) возвращает `null`.   diff --git a/API/scriptChains.md b/API/scriptChains.md index 7465f10..4d08e7d 100644 --- a/API/scriptChains.md +++ b/API/scriptChains.md @@ -299,7 +299,7 @@ run(): TaskPromise | null; На данный момент существует защита от погружения в бесконечную рекурсию и задача, запущенная через `run()`, не может сама использовать этот метод. -Запущенный таким образом скрипт будет считаться порожденным системой и получит системный идентификатоп [запроса](../appendix/glossary.md#request), даже если родительский скрипт был запущен пользователем. +Запущенный таким образом скрипт будет считаться порождённым системой и получит системный идентификатор [запроса](../appendix/glossary.md#request), даже если родительский скрипт был запущен пользователем. Если до запуска скрипта был вызван метод `withPromise(true)`, возвращает [`TaskPromise`](#task-promise), иначе — `null`. @@ -321,7 +321,7 @@ interface CodeExecutionAction extends BaseCodeExecutionAction { setTimeLimit(value: number): this; } ``` -Интерфейс действия запуска динамического кода. Наследуется от [`BaseCodeExecutionAction`](#base-code-execution-action). Скрипты, порождённые этим интерфейсом, не имеют имени и [long id](./common.md#long-id). +Интерфейс действия запуска динамического кода. Наследуется от [`BaseCodeExecutionAction`](#base-code-execution-action). Скрипты, порождённые этим интерфейсом, не имеют имени и [`longId`](./views.md#long-id).   diff --git a/appendix/glossary.md b/appendix/glossary.md index 6d1348c..b2cf87f 100644 --- a/appendix/glossary.md +++ b/appendix/glossary.md @@ -22,9 +22,9 @@ - список шагов выполнения запроса, - результат запроса (ответ на запрос). -Запросы могут быть порождены пользователями, тогда они имеют идентификаторы вида `%латинские буквы и цифры%-%число, отличное от ноля%`. Если запрос порождён системой, то он будет иметь идентификатор вида `%латинские буквы и цифры%-0`. +Запросы могут быть порождены пользователями, тогда они имеют идентификаторы вида `%латинские буквы и цифры%-%число, отличное от нуля%`. Если запрос порождён системой, то он будет иметь идентификатор вида `%латинские буквы и цифры%-0`. Скрипты, запущенные по расписанию или через систему API сервисов считаются системными. -Запрос регистрируется в системе и может быть отменён, а также запрос будет залогирован в системные журналы. Список активных запросов можно увидеть в панели администратора в разделе `Requests` -> `Requests`. Рядом находится вкладка истории **пользовательских** запросов (системных запросов там не найти) — `History`. +Запрос регистрируется в системе и может быть отменён, а также запрос логируется в системные журналы. Список активных запросов можно увидеть в панели администратора в разделе `Requests` на вкладке `Requests`. Рядом находится вкладка `History` с историей **пользовательских** запросов (системных запросов там нет). Системные запросы скриптов можно увидеть в web-интерфейсе Optimacros на вкладке `Macros` -> `Scripts` -> `Launch History`. [Приложения](appendix.md) From 67146d686a5a4d44b47b89a450d1f3efbf6e6793 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Tue, 5 Aug 2025 15:18:57 +0700 Subject: [PATCH 30/37] Export CSV from Clickhouse --- API/clickhouse.md | 8 ++++++++ API/scripts.om.d.ts | 1 + changelog.md | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/API/clickhouse.md b/API/clickhouse.md index fa9021e..a680545 100644 --- a/API/clickhouse.md +++ b/API/clickhouse.md @@ -113,6 +113,7 @@ interface ClickhouseQueryBuilder { insert(values: Object[] | Object): boolean; get(): ClickhouseQueryResult; + exportToCsv(path: string, ignoreHeader?: boolean): number; } ``` Интерфейс построения запроса к базе данных. @@ -298,6 +299,13 @@ get(): ClickhouseQueryResult;   +```js +exportToCsv(path: string, ignoreHeader?: boolean): number; +``` +Конструирует SQL-запрос, передаёт его на исполнение в СУБД и сохраняет результат в CSV формате в [`рабочей директории скрипта`](../appendix/glossary.md#script-dir) в файл `path`. Аргумент `ignoreHeader` устанавливает флаг игнорирования заголовка (`false` по умолчанию). Возвращает количество полученных строк. + +  + ## Интерфейс ClickhouseQueryResult ```ts interface ClickhouseQueryResult { diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index c89d1e2..38a2649 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -1740,6 +1740,7 @@ export interface ClickhouseQueryBuilder { sum(column: string): number; insert(values: Object[] | Object): boolean; get(): ClickhouseQueryResult; + exportToCsv(path: string, ignoreHeader?: boolean): number; } export interface ClickhouseQueryResult { diff --git a/changelog.md b/changelog.md index 2bee552..c7d0491 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,8 @@ | Дата релиза | Тег релиза | Версия ScriptAPI | Версии MiddleWork | Версия приложения | Изменения | | --- | --- | --- | --- | --- | --- | -| 24.03.2025 | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейсе [Filesystem](./API/fs.md#filesystem) изменены декларации функций `delete()`, `rename()`, `copy()`, `createDir()`, `deleteDir()`, `getSize()`
  • В интерфейс [CellBuffer](./API/common.md#cell-buffer) добавлена функция `lastApplyErrors()`
  • В интерфейсе [Importer](./API/exportImport.md#importer) исправлены исключения, которые могут бросать функции
| +| - | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейс [Connectors](./API/connectors.md#connectors) добавлен коннектор [`ClickhouseConnectorBuilder`](./clickhouse.md#clickhouse-connector-builder) для подключения к базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse)
| +| - | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейсе [Filesystem](./API/fs.md#filesystem) изменены декларации функций `delete()`, `rename()`, `copy()`, `createDir()`, `deleteDir()`, `getSize()`
  • В интерфейс [CellBuffer](./API/common.md#cell-buffer) добавлена функция `lastApplyErrors()`
  • В интерфейсе [Importer](./API/exportImport.md#importer) исправлены исключения, которые могут бросать функции
| | 16.12.2024 | [v9.?00.x.x](https://github.com/optimacros/scripts_documentation/tree/v9.?00.x.x) | — |
  • 9.?00.x.x
|
|
  • Интерфейс работы с лицензиями воркспейса `EnterpriseLicenseManager` заменён на новый интерфейс работы с данными договора о параметрах воркспейса [EnterpriseContractManager](./API/common.md#enterprise-contract-manager)
| | 15.11.2024 | [9.200.x.13](https://github.com/optimacros/scripts_documentation/tree/v9.200.x.13) | — |
  • 9.200.dev.13
|
  • 9.200.x.x
|
  • В интерфейс [Filesystem](./API/fs.md#filesystem) добавлен метод для изменения кодировки файла `changeTextFileCharset()`
| | 26.09.2024 | [v9.0.50.6](https://github.com/optimacros/scripts_documentation/tree/v9.0.50.6) | — |
  • 9.0.50.6
|
  • 9.54.10
|
  • В интерфейс [Connectors](./API/connectors.md#connectors) добавлен коннектор для подключения к базе данных `Vertica`
| From 09768e2ed14be6e865ae7addc7bc79d60ab60ae9 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Wed, 24 Sep 2025 15:29:08 +0700 Subject: [PATCH 31/37] - Added secrets for Clickhouse connection --- API/clickhouse.md | 22 +++++++++++----------- API/scripts.om.d.ts | 10 +++++----- API/secrets.md | 2 +- API/views.md | 2 +- changelog.md | 4 ++-- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/API/clickhouse.md b/API/clickhouse.md index a680545..7187bbd 100644 --- a/API/clickhouse.md +++ b/API/clickhouse.md @@ -3,49 +3,49 @@ ## Интерфейс ClickhouseConnectorBuilder ```ts interface ClickhouseConnectorBuilder { - setHost(value: string): this; - setPort(value: number): this; - setUsername(value: string): this; - setPassword(value: string): this; - setDatabase(value: string): this; + setHost(value: string | SecretValue): this; + setPort(value: number | SecretValue): this; + setUsername(value: string | SecretValue): this; + setPassword(value: string | SecretValue): this; + setDatabase(value: string | SecretValue): this; setHttps(value: boolean): this; load(): ClickhouseConnection; } ``` -Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), базовый интерфейс [`коннекторов`](../appendix/glossary.md#connector) для настройки подключения к базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse). +Интерфейс, реализующий шаблон проектирования [`строитель`](https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%B8%D1%82%D0%B5%D0%BB%D1%8C_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)), базовый интерфейс [`коннекторов`](../appendix/glossary.md#connector) для настройки подключения к базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse). Функции для установки параметров подключения поддерживают [секреты](./secrets.md).   ```js -setHost(value: string): this; +setHost(value: string | SecretValue): this; ``` Устанавливает адрес подключения. Возвращает `this`.   ```js -setPort(value: number): this; +setPort(value: number | SecretValue): this; ``` Устанавливает номер порта для подключения. Возвращает `this`.   ```js -setUsername(value: string): this; +setUsername(value: string | SecretValue): this; ``` Устанавливает имя пользователя. Возвращает `this`.   ```js -setPassword(value: string): this; +setPassword(value: string | SecretValue): this; ``` Устанавливает пароль. Возвращает `this`.   ```js -setDatabase(value: string): this; +setDatabase(value: string | SecretValue): this; ``` Устанавливает имя базы данных. Возвращает `this`. diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 3036472..027f6be 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -1687,11 +1687,11 @@ export interface SnowflakeConnectorBuilder extends SqlConnectorBuilder { } export interface ClickhouseConnectorBuilder { - setHost(value: string): this; - setPort(value: number): this; - setUsername(value: string): this; - setPassword(value: string): this; - setDatabase(value: string): this; + setHost(value: string | SecretValue): this; + setPort(value: number | SecretValue): this; + setUsername(value: string | SecretValue): this; + setPassword(value: string | SecretValue): this; + setDatabase(value: string | SecretValue): this; setHttps(value: boolean): this; load(): ClickhouseConnection; } diff --git a/API/secrets.md b/API/secrets.md index 920916c..5142d6c 100644 --- a/API/secrets.md +++ b/API/secrets.md @@ -66,7 +66,7 @@ interface SecretValue { ``` Объект секрета. **Обратите внимание, что значения секрета в объекте нет.** Он только содержит информацию о секрете. Так сделано для безопасности. Значения считываются из хранилища только внутри приложения Optimacros. Этот объект нужно передавать в методы API скриптов, которые поддерживают секреты, вместо простых типов данных. Приложение само сделает запрос в хранилище и подставит значение секрета. -Опознать методы с поддержкой секретов можно по сигнатуре `setPassword(password: string | SecretValue): this;`. Появляется выбор - использовать простой тип данных или секрет. Проверка типа значения также работает на секретах. Если тип не совпадет с тем, который ожидается методом, вылетит ошибка. +Опознать методы с поддержкой секретов можно по сигнатуре `setPassword(password: string | SecretValue): this;`. Появляется выбор - использовать простой тип данных или секрет. Проверка типа значения также работает на секретах. Если тип не совпадет с тем, который ожидается методом, выбрасывается ошибка.   diff --git a/API/views.md b/API/views.md index dc339fd..5b6b587 100644 --- a/API/views.md +++ b/API/views.md @@ -221,7 +221,7 @@ rowsFilter(data: string | string[] | number | number[]): this; `number[]` — массив [`longId`](./common.md#long-id) строк. -Максимальный размер фильтра: `50000` строк. Если передать в фильтре большее количество строк, то метод не выбрасывает ошибку, но грид будет отфильтрован только по первым `50000` строкам в фильтре. +Максимальный размер фильтра: `50000` строк. Если передать в фильтре большее количество строк, то метод не выбрасывает ошибку, но грид будет отфильтрован только по первым `50000` строкам фильтра. Возвращает `this`. diff --git a/changelog.md b/changelog.md index cd64568..892fd3d 100644 --- a/changelog.md +++ b/changelog.md @@ -2,8 +2,8 @@ | Дата релиза | Тег релиза | Версия ScriptAPI | Версии MiddleWork | Версия приложения | Изменения | | --- | --- | --- | --- | --- | --- | -| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейс [Connectors](./API/connectors.md#connectors) добавлен коннектор [`ClickhouseConnectorBuilder`](./clickhouse.md#clickhouse-connector-builder) для подключения к базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse)
| -| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейс информации о запросе [`RequestManager`](./API/common.md#request-manager) добавлены методы для получения информации о текущем исполняемом скрипте: `getRequestId()`, `getScriptName()`, `getScriptLongId()`
| +| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Добавлен интерфейс [Secrets](./API/secrets.md) для взаимодействия с сервисом защищённого хранилища секретов
  • В интерфейс [Connectors](./API/connectors.md#connectors) добавлен коннектор [`ClickhouseConnectorBuilder`](./clickhouse.md#clickhouse-connector-builder) для подключения к базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse)
| +| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейс информации о запросе [RequestManager](./API/common.md#request-manager) добавлены методы для получения информации о текущем исполняемом скрипте: `getRequestId()`, `getScriptName()`, `getScriptLongId()`
| | xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Интерфейсы доступа к содержимому грида — заголовкам и ячейкам — вынесены в отдельный файл [readingGrid.md](./API/readingGrid.md)
  • **Обратно несовместимо** переработан метод [`EntitiesInfo.getCollection()`](./API/common.md#entities-info.get-collection)
  • Добавлен интерфейс постраничного получения списка опций значений клетки [DropDownSelector](./API/readingGrid.md#dropdown-selector) и метод для получения доступа к нему — [`Cell.dropDownSelector()`](./API/readingGrid.md#cell.dropdown-selector)
  • Метод получения списка опций значений клетки [`Cell.dropDown()`](./API/readingGrid.md#cell.dropdown) признан устаревшим
| | xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейсе [Filesystem](./API/fs.md#filesystem) изменены декларации функций `delete()`, `rename()`, `copy()`, `createDir()`, `deleteDir()`, `getSize()`
  • В интерфейс [CellBuffer](./API/common.md#cell-buffer) добавлена функция `lastApplyErrors()`
  • В интерфейсе [Importer](./API/exportImport.md#importer) исправлены исключения, которые могут бросать функции
| | 07.07.2025 | [v9.200](https://github.com/optimacros/scripts_documentation/tree/v9.?00.x.x) | — |
  • 9.200
|
  • 9.200
|
  • Интерфейс работы с лицензиями воркспейса `EnterpriseLicenseManager` заменён на новый интерфейс работы с данными договора о параметрах воркспейса [EnterpriseContractManager](./API/common.md#enterprise-contract-manager)
| From 90e02146584ded36b7d70073646cc877863caeeb Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Sat, 1 Nov 2025 14:01:06 +0700 Subject: [PATCH 32/37] - Updates for clickhouse - Updates for user roles --- API/clickhouse.md | 29 ++++++++++++++--------------- API/scripts.om.d.ts | 6 +++--- API/users.md | 4 ++-- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/API/clickhouse.md b/API/clickhouse.md index 7187bbd..5d23eff 100644 --- a/API/clickhouse.md +++ b/API/clickhouse.md @@ -99,17 +99,16 @@ interface ClickhouseQueryBuilder { whereBetween(column: string, minValue: string | number, maxValue: string | number, concatOperator?: string, not?: boolean): this; orWhereBetween(column: string, minValue: string | number, maxValue: string | number): this; - groupBy(column: string): this; orderBy(column: string, direction?: string): this; orderByDesc(column: string): this; - - offset(value: number): this; - limit(value: number): this; + groupBy(column: string): this; + limit(count: number, offset?: number): this; exists(): boolean; count(): number; sum(column: string): number; + truncate(): boolean; insert(values: Object[] | Object): boolean; get(): ClickhouseQueryResult; @@ -212,13 +211,6 @@ orWhereBetween(column: string, minValue: string | number, maxValue: string | num   -```js -groupBy(column: string): this; -``` -Устанавливает группировку по колонке `column`. Возвращает `this`. - -  - ```js orderBy(column: string, direction?: string): this; ``` @@ -236,16 +228,16 @@ orderByDesc(column: string): this;   ```js -offset(value: number): this; +groupBy(column: string): this; ``` -Устанавливает количество строк, которое надо пропустить, прежде чем начать выборку. Возвращает `this`. +Устанавливает группировку по колонке `column`. Возвращает `this`.   ```js -limit(value: number): this; +limit(count: number, offset?: number): this; ``` -Устанавливает максимальное количество строк в выборке. Возвращает `this`. +Устанавливает максимальное количество строк в выборке и количество строк, которое надо пропустить, прежде чем начать выборку. По умолчанию выборка начинается с первой строки (аргумент `offset` равен `0`). Возвращает `this`.   @@ -270,6 +262,13 @@ sum(column: string): number;   +```js +truncate(): boolean; +``` +Очищает таблицу запросом `TRUNCATE`. В случае успешного выполнения операции возвращает `true`, иначе `false`. + +  + ```js insert(values: Object[] | Object): boolean; ``` diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index 027f6be..d8fefa8 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -1745,18 +1745,18 @@ export interface ClickhouseQueryBuilder { */ whereBetween(column: string, minValue: string | number, maxValue: string | number, concatOperator?: string, not?: boolean): this; orWhereBetween(column: string, minValue: string | number, maxValue: string | number): this; - groupBy(column: string): this; /** * @param column * @param direction 'ASC'|'DESC'; Default is 'ASC' */ orderBy(column: string, direction?: string): this; orderByDesc(column: string): this; - offset(value: number): this; - limit(value: number): this; + groupBy(column: string): this; + limit(count: number, offset?: number): this; exists(): boolean; count(): number; sum(column: string): number; + truncate(): boolean; insert(values: Object[] | Object): boolean; get(): ClickhouseQueryResult; exportToCsv(path: string, ignoreHeader?: boolean): number; diff --git a/API/users.md b/API/users.md index 40ab751..3ee1472 100644 --- a/API/users.md +++ b/API/users.md @@ -31,7 +31,7 @@ interface ModelUsersTab extends Tab { } ``` Вкладка `Пользователи модели`. Содержит пользователей модели и их настройки. Интерфейс наследуется от [`Tab`](./views.md#tab). -При изменении роли пользователя на `No Access` пользователь будет перемещен на вкладку `Другие пользователи сервера`, но изменение других полей этого же пользователя можно сделать в рамках того же генератора, в котором была изменена его роль. +При изменении роли пользователя на `No Access` или удалении всех его ролей пользователь будет перемещен на вкладку `Другие пользователи сервера`, но изменение других полей этого же пользователя можно сделать в рамках того же генератора, в котором была изменена его роль. [`Grid`](./views.md#grid) данного [`Tab`](./views.md#tab) доступен только пользователям с правами моделера. Со слов разработчиков [`Grid`](./views.md#grid) построен на том же справочнике, который находится в колонках настрек `UAM` пользлвательских справочников. Но `longId` этих измерений, полученные методами данного `API`, могут не совпадать. @@ -60,7 +60,7 @@ interface WorkspaceUsersTab extends Tab { Данный интерфейс и его [`Pivot`](./views.md#интерфейс-pivot) доступны только пользователям с правами администратора. -При изменение роли пользователя на любую кроме `No Acsess` пользователь будет перемещен на вкладку `Пользователи модели`, но изменение других полей этого же пользователя можно сделать в рамках того же генератора, в котором была изменена его роль. +При изменение роли пользователя на любую кроме `No Access` пользователь будет перемещен на вкладку `Пользователи модели`, но изменение других полей этого же пользователя можно сделать в рамках того же генератора, в котором была изменена его роль. [`Pivot`](./views.md#интерфейс-pivot) данного [`Tab`](./views.md#tab) построен на системном справочнике `User Workspace`, который аналогичен вкладке `Users` в панели администратора воркспейса и содержит всех пользователей воркспейса кроме тех, у кого есть доступ к данной модели. У данной таблицы есть только представление по умолчанию (доступно через вызов `pivot()` без аргументов или с аргументом `null`). From a378f4f314a1074e05a3bd0133da40f7f632dde5 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Thu, 13 Nov 2025 13:30:19 +0700 Subject: [PATCH 33/37] Small update for secrets --- API/secrets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/secrets.md b/API/secrets.md index 5142d6c..80cfbf5 100644 --- a/API/secrets.md +++ b/API/secrets.md @@ -51,7 +51,7 @@ getSecret(path: string, key: string): SecretValue; |--- secret-key-2 ``` -Получить секрет можно, зная "папку" `path`, в которой он лежит, и название ключа секрета `key`. +Получить секрет можно, зная путь `path`, по которому он лежит в хранилище, и название ключа секрета `key`.   From 75119b9d313bb05c766686658f0f6e1c85980a10 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Sat, 27 Dec 2025 16:30:28 +0800 Subject: [PATCH 34/37] - New methods for MC/List standart export: set decimal separator and save visual settings --- API/exportImport.md | 31 ++++++++++++++++++++++--------- API/scripts.om.d.ts | 3 ++- changelog.md | 1 + 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/API/exportImport.md b/API/exportImport.md index a5e4984..a811433 100644 --- a/API/exportImport.md +++ b/API/exportImport.md @@ -20,8 +20,10 @@ interface Exporter { setDelimiter(delimiter: string): this; setEnclosure(enclosure: string): this; setEscape(escape: string): this; + setDecimalSeparator(decimalSeparator: string): this; setShowAliasesWithoutNames(showAliasesWithoutNames: boolean): this; setUseCodeLikeLabels(useCodeLikeLabels: boolean): this; + setSaveVisualSettings(saveVisualSettings: boolean): this; export(): ExportResult; } ``` @@ -107,6 +109,13 @@ setEscape(escape: string): this;   +```js +setDecimalSeparator(decimalSeparator: string): this; +``` +Устанавливает десятичный разделитель. Допустимые значения: `,`, `.`. По умолчанию: `.`. + +  + ```js setShowAliasesWithoutNames(showAliasesWithoutNames: boolean): this; ``` @@ -121,6 +130,19 @@ setUseCodeLikeLabels(useCodeLikeLabels: boolean): this;   +```js +setSaveVisualSettings(saveVisualSettings: boolean): this; +``` +Устанавливает флаг сохранения визуальных настроек для формата `XLSX`, без учета `CV`. + +- Ширина заголовка колонок в XLSX файле - соответствует установленной в веб-интерфейсе. +- Уровни иерархии справочников выделены жирным шрифтом по аналогии с веб-интерфейсом. +- Уровни иерархии справочников визуально выделяются "лесенкой" по аналогии с веб-интерфейсом. +- Процентные показатели в XLSX файле – отображены в виде процентов. +- Выгружается выбранное количество значащих десятичных разрядов. + +  + ```js export(): ExportResult; @@ -134,10 +156,8 @@ export(): ExportResult; interface StorageExporter extends Exporter { setFormat(format: string): this; setDelimiter(delimiter: string): this; - setLineDelimiter(lineDelimiter: string): this; setFilterFormula(filterFormula: string): this; - setDecimalSeparator(decimalSeparator: string): this; setDateFormat(dateFormat: string): this; setBooleanCubeIdentifier(booleanCubeIdentifier: number): this; } @@ -175,13 +195,6 @@ setFilterFormula(filterFormula: string): this;   -```js -setDecimalSeparator(decimalSeparator: string): this; -``` -Устанавливает десятичный разделитель. Допустимые значения: `,`, `.`. По умолчанию: `.`. - -  - ```js setDateFormat(dateFormat: string): this; diff --git a/API/scripts.om.d.ts b/API/scripts.om.d.ts index d8fefa8..c1245ab 100644 --- a/API/scripts.om.d.ts +++ b/API/scripts.om.d.ts @@ -141,8 +141,10 @@ export interface Exporter { setDelimiter(delimiter: string): this; setEnclosure(enclosure: string): this; setEscape(escape: string): this; + setDecimalSeparator(decimalSeparator: string): this; setShowAliasesWithoutNames(showAliasesWithoutNames: boolean): this; setUseCodeLikeLabels(useCodeLikeLabels: boolean): this; + setSaveVisualSettings(saveVisualSettings: boolean): this; export(): ExportResult; } @@ -244,7 +246,6 @@ export interface CubeInfo extends EntityInfo { export interface StorageExporter extends Exporter { setLineDelimiter(lineDelimiter: string): this; setFilterFormula(filterFormula: string): this; - setDecimalSeparator(decimalSeparator: string): this; setDateFormat(dateFormat: string): this; setBooleanCubeIdentifier(booleanCubeIdentifier: number): this; } diff --git a/changelog.md b/changelog.md index 892fd3d..f9f4041 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ | Дата релиза | Тег релиза | Версия ScriptAPI | Версии MiddleWork | Версия приложения | Изменения | | --- | --- | --- | --- | --- | --- | +| xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейс экспорта из мультикубов и справочников [Exporter](./API/exportImport.md#exporter) добавлены методы `setDecimalSeparator()` и `setSaveVisualSettings()`
| | xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Добавлен интерфейс [Secrets](./API/secrets.md) для взаимодействия с сервисом защищённого хранилища секретов
  • В интерфейс [Connectors](./API/connectors.md#connectors) добавлен коннектор [`ClickhouseConnectorBuilder`](./clickhouse.md#clickhouse-connector-builder) для подключения к базе данных [`Clickhouse`](https://ru.wikipedia.org/wiki/ClickHouse)
| | xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • В интерфейс информации о запросе [RequestManager](./API/common.md#request-manager) добавлены методы для получения информации о текущем исполняемом скрипте: `getRequestId()`, `getScriptName()`, `getScriptLongId()`
| | xx.xx.xxxx | [v9.300](https://github.com/optimacros/scripts_documentation/tree/v9.300) | — |
  • 9.300
|
|
  • Интерфейсы доступа к содержимому грида — заголовкам и ячейкам — вынесены в отдельный файл [readingGrid.md](./API/readingGrid.md)
  • **Обратно несовместимо** переработан метод [`EntitiesInfo.getCollection()`](./API/common.md#entities-info.get-collection)
  • Добавлен интерфейс постраничного получения списка опций значений клетки [DropDownSelector](./API/readingGrid.md#dropdown-selector) и метод для получения доступа к нему — [`Cell.dropDownSelector()`](./API/readingGrid.md#cell.dropdown-selector)
  • Метод получения списка опций значений клетки [`Cell.dropDown()`](./API/readingGrid.md#cell.dropdown) признан устаревшим
| From 50fc5e4e938fefbb9955eb2104070d49cb475c46 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Sat, 27 Dec 2025 17:13:31 +0800 Subject: [PATCH 35/37] Small update --- API/exportImport.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/exportImport.md b/API/exportImport.md index a811433..658892c 100644 --- a/API/exportImport.md +++ b/API/exportImport.md @@ -137,7 +137,7 @@ setSaveVisualSettings(saveVisualSettings: boolean): this; - Ширина заголовка колонок в XLSX файле - соответствует установленной в веб-интерфейсе. - Уровни иерархии справочников выделены жирным шрифтом по аналогии с веб-интерфейсом. -- Уровни иерархии справочников визуально выделяются "лесенкой" по аналогии с веб-интерфейсом. +- Уровни иерархии справочников визуально выделяются "лесенкой" по аналогии с веб-интерфейсом. Импорт такого файла с итоговыми строками на данный момент недоступен. - Процентные показатели в XLSX файле – отображены в виде процентов. - Выгружается выбранное количество значащих десятичных разрядов. From 69e049eb4a98e40074b508987e15de5558b72e6c Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Tue, 30 Dec 2025 15:58:22 +0800 Subject: [PATCH 36/37] Corrected after review --- API/exportImport.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/API/exportImport.md b/API/exportImport.md index 658892c..550e25e 100644 --- a/API/exportImport.md +++ b/API/exportImport.md @@ -114,6 +114,8 @@ setDecimalSeparator(decimalSeparator: string): this; ``` Устанавливает десятичный разделитель. Допустимые значения: `,`, `.`. По умолчанию: `.`. +Для форматов `'xls'` и `'xlsx'` нужно устанавливать разделитель `.`, иначе Excel трактует значение как текст. Отображаемый разделитель в Excel зависит от системных настроек компьютера и настроек самого Excel. +   ```js @@ -133,11 +135,11 @@ setUseCodeLikeLabels(useCodeLikeLabels: boolean): this; ```js setSaveVisualSettings(saveVisualSettings: boolean): this; ``` -Устанавливает флаг сохранения визуальных настроек для формата `XLSX`, без учета `CV`. +Устанавливает флаг сохранения визуальных настроек для формата `'xlsx'`, без учета `CV`. - Ширина заголовка колонок в XLSX файле - соответствует установленной в веб-интерфейсе. - Уровни иерархии справочников выделены жирным шрифтом по аналогии с веб-интерфейсом. -- Уровни иерархии справочников визуально выделяются "лесенкой" по аналогии с веб-интерфейсом. Импорт такого файла с итоговыми строками на данный момент недоступен. +- Уровни иерархии справочников визуально выделяются "лесенкой" по аналогии с веб-интерфейсом. Импорт такого файла с итоговыми строками обратно в OM на данный момент недоступен. - Процентные показатели в XLSX файле – отображены в виде процентов. - Выгружается выбранное количество значащих десятичных разрядов. From 28f7dc2bde180f570da51692e8347df28de8e7d4 Mon Sep 17 00:00:00 2001 From: mgolovkina Date: Fri, 20 Feb 2026 14:04:15 +0700 Subject: [PATCH 37/37] Documentation update for getting files in API services --- API/apiService.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/apiService.md b/API/apiService.md index 45948f9..4e015f7 100644 --- a/API/apiService.md +++ b/API/apiService.md @@ -159,7 +159,7 @@ interface RequestFileInfos { getAll(): RequestFileInfo[]; } ``` -Интерфейс для получения информации о загруженных клиентом файлах. +Интерфейс для получения информации о загруженных клиентом файлах. Если требуется загрузить несколько файлов в одном ключе, в запросе для каждого файла нужно указывать одинаковый ключ-массив вида `key[]`.