Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions docs/pages/product/apis-integrations/rest-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,13 @@ by making them accessible to specific users only or disallowing access for
everyone. By default, API endpoints in all scopes, except for `jobs`, are
accessible for everyone.

| API scope | REST API endpoints | Accessible by default? |
| --------- | ----------------------------------------------------------------------------------------- | ---------------------- |
| `meta` | [`/v1/meta`][ref-ref-meta] | ✅ Yes |
| `data` | [`/v1/load`][ref-ref-load], [`/v1/sql`][ref-ref-sql] | ✅ Yes |
| `graphql` | `/graphql` | ✅ Yes |
| `jobs` | [`/v1/pre-aggregations/jobs`][ref-ref-paj] | ❌ No |
| No scope | `/livez`, `/readyz` | ✅ Yes, always |
| API scope | REST API endpoints | Accessible by default? |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| `meta` | [`/v1/meta`][ref-ref-meta], [`/v1/meta/namesModel`][ref-ref-meta-names-model], [`/v1/meta/:nameModel`][ref-ref-meta-name-model], [`/v1/meta/:nameModel/:field`][ref-ref-meta-name-model-field] | ✅ Yes |
| `data` | [`/v1/load`][ref-ref-load], [`/v1/sql`][ref-ref-sql] | ✅ Yes |
| `graphql` | `/graphql` | ✅ Yes |
| `jobs` | [`/v1/pre-aggregations/jobs`][ref-ref-paj] | ❌ No |
| No scope | `/livez`, `/readyz` | ✅ Yes, always |

You can set accessible API scopes _for all requests_ using the
`CUBEJS_DEFAULT_API_SCOPES` environment variable. For example, to disallow
Expand Down Expand Up @@ -278,6 +278,9 @@ example, the following query will retrieve rows 101-200 from the `Orders` cube:
/reference/configuration/config#contexttoapiscopes
[ref-ref-load]: /product/apis-integrations/rest-api/reference#base_pathv1load
[ref-ref-meta]: /product/apis-integrations/rest-api/reference#base_pathv1meta
[ref-ref-meta-names-model]: /product/apis-integrations/rest-api/reference#base_pathv1metanamesmodel
[ref-ref-meta-name-model]: /product/apis-integrations/rest-api/reference#base_pathv1metanamemodel
[ref-ref-meta-name-model-field]: /product/apis-integrations/rest-api/reference#base_pathv1metanamemodelfield
[ref-ref-sql]: /product/apis-integrations/rest-api/reference#base_pathv1sql
[ref-ref-paj]: /product/apis-integrations/rest-api/reference#base_pathv1pre-aggregationsjobs
[ref-security-context]: /product/auth/context
Expand All @@ -290,4 +293,4 @@ example, the following query will retrieve rows 101-200 from the `Orders` cube:
[self-cors]: #configuration-cors
[ref-ref-rest-api]: /product/apis-integrations/rest-api/reference
[link-jq-utility]: https://jqlang.github.io/jq/
[gh-cube-openspec]: https://github.com/cube-js/cube/blob/master/packages/cubejs-api-gateway/openspec.yml
[gh-cube-openspec]: https://github.com/cube-js/cube/blob/master/packages/cubejs-api-gateway/openspec.yml
130 changes: 130 additions & 0 deletions docs/pages/product/apis-integrations/rest-api/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,136 @@ Example response:
}
```

## `{base_path}/v1/meta/namesModel`

Get names for cubes and views defined in the data model.

Response

- `cubes` - Array of cubes and views
- `name` - Codename of the cube/view

Example request:

```bash{outputLines: 2-4}
curl \
-H "Authorization: EXAMPLE-API-TOKEN" \
-G \
http://localhost:4000/cubejs-api/v1/meta/namesModel
```

Example response:

```json
{
"cubes": [
{
"name": "Users"
}
]
}
```

## `{base_path}/v1/meta/:nameModel`

Get all the information about cubes or views by knowing the name that is passed to :nameModel.

Response

- `cubes` - Array of cubes and views
- `name` - Codename of the cube/view
- `type` - Type can be "cube" or "view"
- `title` - Human-readable cube/view name
- `meta` - Custom metadata
- `measures` - Array of measures in this cube/view
- `dimensions` - Array of dimensions in this cube/view
- `hierarchies` - Array of hierarchies in this cube
- `segments` - Array of segments in this cube/view
- `folders` - Array of folders in this view
- `connectedComponent` - An integer representing a join relationship. If the same value is returned for two cubes, then there is
at least one join path between them.

Example request:

```bash{outputLines: 2-4}
curl \
-H "Authorization: EXAMPLE-API-TOKEN" \
-G \
http://localhost:4000/cubejs-api/v1/meta/Users
```

Example response:

```json
{
"cubes": [
{
"name": "Users",
"title": "Users",
"meta": {
"someKey": "someValue",
"nested": {
"someKey": "someValue"
}
},
"connectedComponent": 1,
"measures": [
{
"name": "users.count",
"title": "Users Count",
"shortTitle": "Count",
"aliasName": "users.count",
"type": "number",
"aggType": "count",
"drillMembers": ["users.id", "users.city", "users.createdAt"]
}
],
"dimensions": [
{
"name": "users.city",
"title": "Users City",
"type": "string",
"aliasName": "users.city",
"shortTitle": "City",
"suggestFilterValues": true
}
],
"segments": []
}
]
}
```

## `{base_path}/v1/meta/:nameModel/:field`

Get information :field about cubes or views, knowing the name that is passed to :nameModel.

Response

- `value` - Value from the field by cubes or views

Example request:

```bash{outputLines: 2-4}
curl \
-H "Authorization: EXAMPLE-API-TOKEN" \
-G \
http://localhost:4000/cubejs-api/v1/meta/Users/meta
```

Example response:

```json
{
"value": {
"someKey": "someValue",
"nested": {
"someKey": "someValue"
}
}
}
```

## `{base_path}/v1/pre-aggregations/jobs`

Trigger pre-aggregation build jobs or retrieve statuses of such jobs.
Expand Down
177 changes: 135 additions & 42 deletions packages/cubejs-api-gateway/src/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,45 @@ class ApiGateway {
})
);

app.get(
`${this.basePath}/v1/meta/namesModel`,
userMiddlewares,
userAsyncHandler(async (req, res) => {
await this.metaModelName({
context: req.context,
res: this.resToResultFn(res),
});
})
);

app.get(
`${this.basePath}/v1/meta/:nameModel`,
userMiddlewares,
userAsyncHandler(async (req, res) => {
const { nameModel } = req.params;
await this.metaModelByName({
context: req.context,
nameModel,
res: this.resToResultFn(res),
});
})
);

app.get(
`${this.basePath}/v1/meta/:nameModel/:field`,
userMiddlewares,
userAsyncHandler(async (req, res) => {
const { nameModel } = req.params;
const { field } = req.params;
await this.metaFieldModelByName({
context: req.context,
nameModel,
field,
res: this.resToResultFn(res),
});
})
);

app.post(
`${this.basePath}/v1/cubesql`,
userMiddlewares,
Expand Down Expand Up @@ -589,35 +628,54 @@ class ApiGateway {
})).filter(cube => cube.config.measures?.length || cube.config.dimensions?.length || cube.config.segments?.length);
}

private transformCubeConfig(cube: any, cubeDefinitions: any) {
return {
...transformCube(cube, cubeDefinitions),
measures: cube.measures?.map(measure => transformMeasure(measure, cubeDefinitions)),
dimensions: cube.dimensions?.map(dimension => transformDimension(dimension, cubeDefinitions)),
segments: cube.segments?.map(segment => transformSegment(segment, cubeDefinitions)),
joins: transformJoins(cubeDefinitions[cube.name]?.joins),
preAggregations: transformPreAggregations(cubeDefinitions[cube.name]?.preAggregations)
};
}

private extractModelNames(cubes: any[]) {
return cubes.map(cube => ({ name: cube.config?.name }));
}

private async fetchMetaConfig(context: RequestContext, includeCompiler: boolean) {
const compilerApi = await this.getCompilerApi(context);
return compilerApi.metaConfig(context, {
requestId: context.requestId,
includeCompilerId: includeCompiler
});
}

private async fetchMetaConfigExtended(context: ExtendedRequestContext) {
const compilerApi = await this.getCompilerApi(context);
return compilerApi.metaConfigExtended(context, {
requestId: context.requestId
});
}

public async meta({ context, res, includeCompilerId, onlyCompilerId }: {
context: RequestContext,
res: MetaResponseResultFn,
includeCompilerId?: boolean,
onlyCompilerId?: boolean
}) {
const requestStarted = new Date();

try {
await this.assertApiScope('meta', context.securityContext);
const compilerApi = await this.getCompilerApi(context);
const metaConfig = await compilerApi.metaConfig(context, {
requestId: context.requestId,
includeCompilerId: includeCompilerId || onlyCompilerId
});
const metaConfig = await this.fetchMetaConfig(context, !!(includeCompilerId || onlyCompilerId));
if (onlyCompilerId) {
const response: { cubes: any[], compilerId?: string } = {
cubes: [],
compilerId: metaConfig.compilerId
};
res(response);
res({ cubes: [], compilerId: metaConfig.compilerId });
return;
}
const cubesConfig = includeCompilerId ? metaConfig.cubes : metaConfig;
const cubes = this.filterVisibleItemsInMeta(context, cubesConfig).map(cube => cube.config);
const cubes = this.filterVisibleItemsInMeta(context, cubesConfig).map((cube: any) => cube.config);
const response: { cubes: any[], compilerId?: string } = { cubes };
if (includeCompilerId) {
response.compilerId = metaConfig.compilerId;
}
if (includeCompilerId) response.compilerId = metaConfig.compilerId;
res(response);
} catch (e: any) {
this.handleError({
Expand All @@ -632,39 +690,74 @@ class ApiGateway {

public async metaExtended({ context, res }: { context: ExtendedRequestContext, res: ResponseResultFn }) {
const requestStarted = new Date();
try {
await this.assertApiScope('meta', context.securityContext);
const { metaConfig, cubeDefinitions } = await this.fetchMetaConfigExtended(context);
const cubes = this.filterVisibleItemsInMeta(context, metaConfig)
.map((meta: any) => meta.config)
.map((cube: any) => this.transformCubeConfig(cube, cubeDefinitions));
res({ cubes });
} catch (e: any) {
this.handleError({ e, context, res, requestStarted });
}
}

public async metaModelName({ context, res }: { context: ExtendedRequestContext, res: ResponseResultFn }) {
const requestStarted = new Date();
try {
await this.assertApiScope('meta', context.securityContext);
const compilerApi = await this.getCompilerApi(context);
const metaConfigExtended = await compilerApi.metaConfigExtended(context, {
requestId: context.requestId,
});
const { metaConfig, cubeDefinitions } = metaConfigExtended;
const { metaConfig } = await this.fetchMetaConfigExtended(context);
const cubes = this.extractModelNames(metaConfig);
res({ cubes });
} catch (e: any) {
this.handleError({ e, context, res, requestStarted });
}
}

const cubes = this.filterVisibleItemsInMeta(context, metaConfig)
.map((meta) => meta.config)
.map((cube) => ({
...transformCube(cube, cubeDefinitions),
measures: cube.measures?.map((measure) => ({
...transformMeasure(measure, cubeDefinitions),
})),
dimensions: cube.dimensions?.map((dimension) => ({
...transformDimension(dimension, cubeDefinitions),
})),
segments: cube.segments?.map((segment) => ({
...transformSegment(segment, cubeDefinitions),
})),
joins: transformJoins(cubeDefinitions[cube.name]?.joins),
preAggregations: transformPreAggregations(cubeDefinitions[cube.name]?.preAggregations),
}));
private async getModelCubes(context: ExtendedRequestContext, nameModel: string) {
const { metaConfig, cubeDefinitions } = await this.fetchMetaConfigExtended(context);
return this.filterVisibleItemsInMeta(context, metaConfig)
.map((meta: any) => meta.config)
.map((cube: any) => this.transformCubeConfig(cube, cubeDefinitions))
.filter((cube: any) => cube.name === nameModel);
}

public async metaModelByName({ context, nameModel, res }: { context: ExtendedRequestContext, nameModel: string, res: ResponseResultFn }) {
const requestStarted = new Date();
try {
await this.assertApiScope('meta', context.securityContext);
const cubes = await this.getModelCubes(context, nameModel);
if (!cubes.length) {
res({ error: `Model ${nameModel} not found` });
return;
}
res({ cubes });
} catch (e: any) {
this.handleError({
e,
context,
res,
requestStarted,
});
this.handleError({ e, context, res, requestStarted });
}
}

public async metaFieldModelByName({ context, nameModel, field, res }: { context: ExtendedRequestContext, nameModel: string, field?: string, res: ResponseResultFn }) {
const requestStarted = new Date();
try {
await this.assertApiScope('meta', context.securityContext);
const cubes = await this.getModelCubes(context, nameModel);
if (!cubes.length) {
res({ error: `Model ${nameModel} not found` });
return;
}
if (field) {
const cube = cubes[0];
if (!(field in cube)) {
res({ error: `Field ${field} not found in model ${nameModel}` });
return;
}
res({ value: cube[field] });
return;
}
res({ cubes });
} catch (e: any) {
this.handleError({ e, context, res, requestStarted });
}
}

Expand Down
Loading