From 8cfede1052e0f24842eb60e092da26a7332684ca Mon Sep 17 00:00:00 2001 From: Ewan Harris Date: Fri, 28 Jun 2024 13:36:53 +0100 Subject: [PATCH 1/3] chore: changes as a consqeuence of adding to generator This is mostly some whitespace changes, license headers, and reordering of things as a consequence of how this change had to be integrated into the generator --- .openapi-generator/FILES | 1 + api.ts | 62 ++++++++++++++++++++------------------ common.ts | 2 -- credentials/credentials.ts | 4 +++ telemetry.ts | 13 ++++++++ 5 files changed, 51 insertions(+), 31 deletions(-) diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index 7ddd706..fe9c10b 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -36,6 +36,7 @@ example/example1/package.json git_push.sh index.ts package.json +telemetry.ts tests/client.test.ts tests/helpers/default-config.ts tests/helpers/index.ts diff --git a/api.ts b/api.ts index d4acbf6..260e0bc 100644 --- a/api.ts +++ b/api.ts @@ -22,7 +22,8 @@ import { createRequestFunction, RequestArgs, CallResult, - PromiseResult} from "./common"; + PromiseResult +} from "./common"; import { attributeNames } from "./telemetry"; import { Configuration } from "./configuration"; import { Credentials } from "./credentials"; @@ -757,8 +758,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async check(storeId: string, body: CheckRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.check(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, [attributeNames.requestMethod]: "check", + [attributeNames.requestStoreId]: storeId, [attributeNames.user]: body.tuple_key.user }); }, @@ -771,8 +772,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: */ async createStore(body: CreateStoreRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.createStore(body, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestMethod]: "createStore" + return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { + [attributeNames.requestMethod]: "createStore", }); }, /** @@ -784,7 +785,10 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: */ async deleteStore(storeId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.deleteStore(storeId, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials); + return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { + [attributeNames.requestMethod]: "deleteStore", + [attributeNames.requestStoreId]: storeId, + }); }, /** * The Expand API will return all users and usersets that have certain relationship with an object in a certain store. This is different from the `/stores/{store_id}/read` API in that both users and computed usersets are returned. Body parameters `tuple_key.object` and `tuple_key.relation` are all required. The response will return a tree whose leaves are the specific users and usersets. Union, intersection and difference operator are located in the intermediate nodes. ## Example To expand all users that have the `reader` relationship with object `document:2021-budget`, use the Expand API with the following request body ```json { \"tuple_key\": { \"object\": \"document:2021-budget\", \"relation\": \"reader\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` OpenFGA\'s response will be a userset tree of the users and usersets that have read access to the document. ```json { \"tree\":{ \"root\":{ \"type\":\"document:2021-budget#reader\", \"union\":{ \"nodes\":[ { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"users\":{ \"users\":[ \"user:bob\" ] } } }, { \"type\":\"document:2021-budget#reader\", \"leaf\":{ \"computed\":{ \"userset\":\"document:2021-budget#writer\" } } } ] } } } } ``` The caller can then call expand API for the `writer` relationship for the `document:2021-budget`. @@ -797,8 +801,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async expand(storeId: string, body: ExpandRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.expand(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { + [attributeNames.requestMethod]: "expand", [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "expand" }); }, /** @@ -811,8 +815,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async getStore(storeId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.getStore(storeId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "getStore" + [attributeNames.requestMethod]: "getStore", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -826,8 +830,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async listObjects(storeId: string, body: ListObjectsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.listObjects(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, [attributeNames.requestMethod]: "listObjects", + [attributeNames.requestStoreId]: storeId, [attributeNames.user]: body.user }); }, @@ -841,8 +845,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: */ async listStores(pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.listStores(pageSize, continuationToken, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestMethod]: "listStores" + return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { + [attributeNames.requestMethod]: "listStores", }); }, /** @@ -856,8 +860,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async listUsers(storeId: string, body: ListUsersRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.listUsers(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "listUsers" + [attributeNames.requestMethod]: "listUsers", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -871,8 +875,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async read(storeId: string, body: ReadRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.read(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "read" + [attributeNames.requestMethod]: "read", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -886,8 +890,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async readAssertions(storeId: string, authorizationModelId: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.readAssertions(storeId, authorizationModelId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "readAssertions" + [attributeNames.requestMethod]: "readAssertions", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -901,8 +905,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async readAuthorizationModel(storeId: string, id: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.readAuthorizationModel(storeId, id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "readAuthorizationModel" + [attributeNames.requestMethod]: "readAuthorizationModel", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -917,8 +921,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async readAuthorizationModels(storeId: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.readAuthorizationModels(storeId, pageSize, continuationToken, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "readAuthorizationModels" + [attributeNames.requestMethod]: "readAuthorizationModels", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -934,8 +938,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async readChanges(storeId: string, type?: string, pageSize?: number, continuationToken?: string, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.readChanges(storeId, type, pageSize, continuationToken, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "readChanges" + [attributeNames.requestMethod]: "readChanges", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -949,8 +953,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async write(storeId: string, body: WriteRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.write(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "write" + [attributeNames.requestMethod]: "write", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -965,8 +969,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async writeAssertions(storeId: string, authorizationModelId: string, body: WriteAssertionsRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.writeAssertions(storeId, authorizationModelId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "writeAssertions" + [attributeNames.requestMethod]: "writeAssertions", + [attributeNames.requestStoreId]: storeId, }); }, /** @@ -980,8 +984,8 @@ export const OpenFgaApiFp = function(configuration: Configuration, credentials: async writeAuthorizationModel(storeId: string, body: WriteAuthorizationModelRequest, options?: any): Promise<(axios?: AxiosInstance) => PromiseResult> { const localVarAxiosArgs = await localVarAxiosParamCreator.writeAuthorizationModel(storeId, body, options); return createRequestFunction(localVarAxiosArgs, globalAxios, configuration, credentials, { - [attributeNames.requestStoreId]: storeId, - [attributeNames.requestMethod]: "writeAuthorizationModel" + [attributeNames.requestMethod]: "writeAuthorizationModel", + [attributeNames.requestStoreId]: storeId, }); }, }; diff --git a/common.ts b/common.ts index f5b7c2a..f4564c8 100644 --- a/common.ts +++ b/common.ts @@ -14,7 +14,6 @@ import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; import { metrics } from "@opentelemetry/api"; - import { Configuration } from "./configuration"; import type { Credentials } from "./credentials"; import { @@ -230,4 +229,3 @@ export const createRequestFunction = function (axiosArgs: RequestArgs, axiosInst return result; }; }; - diff --git a/credentials/credentials.ts b/credentials/credentials.ts index 8bd4ba6..8374e31 100644 --- a/credentials/credentials.ts +++ b/credentials/credentials.ts @@ -122,6 +122,7 @@ export class Credentials { if (this.accessToken && (!this.accessTokenExpiryDate || this.accessTokenExpiryDate > new Date())) { return this.accessToken; } + return this.refreshAccessToken(); } } @@ -132,6 +133,7 @@ export class Credentials { */ private async refreshAccessToken() { const clientCredentials = (this.authConfig as { method: CredentialsMethod.ClientCredentials; config: ClientCredentialsConfig })?.config; + try { const response = await attemptHttpRequest<{ client_id: string, @@ -162,7 +164,9 @@ export class Credentials { this.accessToken = response.data.access_token; this.accessTokenExpiryDate = new Date(Date.now() + response.data.expires_in * 1000); } + this.tokenCounter?.add(1, buildAttributes(response, this.authConfig)); + return this.accessToken; } catch (err: unknown) { if (err instanceof FgaApiError) { diff --git a/telemetry.ts b/telemetry.ts index 625e6a6..3c34e08 100644 --- a/telemetry.ts +++ b/telemetry.ts @@ -1,3 +1,16 @@ +/** + * JavaScript and Node.js SDK for OpenFGA + * + * API version: 1.x + * Website: https://openfga.dev + * Documentation: https://openfga.dev/docs + * Support: https://openfga.dev/community + * License: [Apache-2.0](https://github.com/openfga/js-sdk/blob/main/LICENSE) + * + * NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. + */ + + import { AxiosResponse } from "axios"; import { Attributes } from "@opentelemetry/api"; import { SEMATTRS_HTTP_HOST, SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_STATUS_CODE } from "@opentelemetry/semantic-conventions"; From 093f938102252747e902ef5509da4e964db93b4b Mon Sep 17 00:00:00 2001 From: Ewan Harris Date: Fri, 28 Jun 2024 13:40:10 +0100 Subject: [PATCH 2/3] release: v0.6.0 --- CHANGELOG.md | 5 +++++ common.ts | 2 +- configuration.ts | 4 ++-- credentials/credentials.ts | 2 +- example/README.md | 2 +- example/example1/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 8 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7877b0a..e2ba64c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.6.0 + +### [0.5.0](https://github.com/openfga/js-sdk/compare/v0.5.0...v0.6.0) (2024-06-28) +- feat: add opentelemetry metrics reporting + ## v0.5.0 ### [0.5.0](https://github.com/openfga/js-sdk/compare/v0.4.0...v0.5.0) (2024-06-14) diff --git a/common.ts b/common.ts index f4564c8..8ca15c1 100644 --- a/common.ts +++ b/common.ts @@ -28,7 +28,7 @@ import { import { setNotEnumerableProperty } from "./utils"; import { buildAttributes } from "./telemetry"; -const meter = metrics.getMeter("@openfga/sdk", "0.5.0"); +const meter = metrics.getMeter("@openfga/sdk", "0.6.0"); const durationHist = meter.createHistogram("fga-client.request.duration", { description: "The duration of requests", unit: "milliseconds", diff --git a/configuration.ts b/configuration.ts index b5e8c99..de71d85 100644 --- a/configuration.ts +++ b/configuration.ts @@ -21,7 +21,7 @@ const DEFAULT_MAX_RETRY = 15; // default minimum wait period in retry - but will backoff exponentially const DEFAULT_MIN_WAIT_MS = 100; -const DEFAULT_USER_AGENT = "openfga-sdk js/0.5.0"; +const DEFAULT_USER_AGENT = "openfga-sdk js/0.6.0"; export interface RetryParams { maxRetry?: number; @@ -73,7 +73,7 @@ export class Configuration { * @type {string} * @memberof Configuration */ - private static sdkVersion = "0.5.0"; + private static sdkVersion = "0.6.0"; /** * provide the full api URL (e.g. `https://api.fga.example`) diff --git a/credentials/credentials.ts b/credentials/credentials.ts index 8374e31..ec89da3 100644 --- a/credentials/credentials.ts +++ b/credentials/credentials.ts @@ -52,7 +52,7 @@ export class Credentials { } break; case CredentialsMethod.ClientCredentials: { - const meter = metrics.getMeter("@openfga/sdk", "0.5.0"); + const meter = metrics.getMeter("@openfga/sdk", "0.6.0"); this.tokenCounter = meter.createCounter("fga-client.credentials.request"); break; } diff --git a/example/README.md b/example/README.md index ab6f011..504c0cc 100644 --- a/example/README.md +++ b/example/README.md @@ -28,7 +28,7 @@ Steps 2. In the Example `package.json` change the `@openfga/sdk` dependency from a semver range like below ```json "dependencies": { - "@openfga/sdk": "^0.5.0" + "@openfga/sdk": "^0.6.0" } ``` to a `file:` reference like below diff --git a/example/example1/package.json b/example/example1/package.json index 7debf8f..96e3f20 100644 --- a/example/example1/package.json +++ b/example/example1/package.json @@ -9,7 +9,7 @@ "start": "node example1.mjs" }, "dependencies": { - "@openfga/sdk": "^0.5.0" + "@openfga/sdk": "^0.6.0" }, "engines": { "node": ">=16.13.0" diff --git a/package-lock.json b/package-lock.json index ef4e451..1eccb37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@openfga/sdk", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@openfga/sdk", - "version": "0.5.0", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.9.0", diff --git a/package.json b/package.json index 4de67fc..69d502c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openfga/sdk", - "version": "0.5.0", + "version": "0.6.0", "description": "JavaScript and Node.js SDK for OpenFGA", "author": "OpenFGA", "keywords": [ From 847c456fbe20e5800ae53eee1867bb1f579b75ed Mon Sep 17 00:00:00 2001 From: Ewan Harris Date: Fri, 28 Jun 2024 16:19:04 +0100 Subject: [PATCH 3/3] docs: include information about the opentelemetry data produced --- .openapi-generator/FILES | 1 + CHANGELOG.md | 4 ++-- README.md | 5 +++++ docs/opentelemetry.md | 31 +++++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 docs/opentelemetry.md diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index fe9c10b..9a1a0af 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -28,6 +28,7 @@ configuration.ts credentials/credentials.ts credentials/index.ts credentials/types.ts +docs/opentelemetry.md errors.ts example/Makefile example/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ba64c..a9bc521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ ## v0.6.0 -### [0.5.0](https://github.com/openfga/js-sdk/compare/v0.5.0...v0.6.0) (2024-06-28) -- feat: add opentelemetry metrics reporting +### [0.6.0](https://github.com/openfga/js-sdk/compare/v0.5.0...v0.6.0) (2024-06-28) +- feat: add opentelemetry metrics reporting (#117) ## v0.5.0 diff --git a/README.md b/README.md index e3a35eb..afe29b3 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ This is an autogenerated JavaScript SDK for OpenFGA. It provides a wrapper aroun - [Retries](#retries) - [API Endpoints](#api-endpoints) - [Models](#models) + - [OpenTelemetry](#opentelemetry) - [Contributing](#contributing) - [Issues](#issues) - [Pull Requests](#pull-requests) @@ -711,6 +712,10 @@ const fgaClient = new OpenFgaClient({ [Models](https://github.com/openfga/js-sdk/blob/main/apiModel.ts) +### OpenTelemetry + +This SDK supports producing metrics that can be consumed as part of an [OpenTelemetry](https://opentelemetry.io/) setup. For more information, please see [the documentation]((https://github.com/openfga/js-sdk/blob/main/docs/opentelemetry.md) + ## Contributing ### Issues diff --git a/docs/opentelemetry.md b/docs/opentelemetry.md new file mode 100644 index 0000000..5b9dbd5 --- /dev/null +++ b/docs/opentelemetry.md @@ -0,0 +1,31 @@ +# OpenTelemetry + +This SDK produces [metrics](https://opentelemetry.io/docs/concepts/signals/metrics/) using [OpenTelemetry](https://opentelemetry.io/) that allow you to view data such as request timings. These metrics also include attributes for the model and store ID, as well as the API called to allow you to build reporting. + +When an OpenTelemetry SDK instance is configured, the metrics will be exported and sent to the collector configured as part of your applications configuration. If you are not using OpenTelemetry, the metric functionality is a no-op and the events are never sent. + +In cases when metrics events are sent, they will not be viewable outside of infrastructure configured in your application, and are never available to the OpenFGA team or contributors. + +## Metrics + +### Supported Metrics + +| Metric Name | Type | Description | +|---------------------------------|-----------|---------------------------------------------------------------------------------| +| `fga-client.request.duration` | Histogram | The total request time for FGA requests | +| `fga-client.query.duration` | Histogram | The amount of time the FGA server took to process the request | +|` fga-client.credentials.request`| Counter | The total number of times a new token was requested when using ClientCredentials| + +### Supported attributes + +| Attribute Name | Type | Description | +|--------------------------------|----------|-------------------------------------------------------------------------------------| +| `fga-client.response.model_id` | `string` | The authorization model ID that the FGA server used | +| `fga-client.request.method` | `string` | The FGA method/action that was performed | +| `fga-client.request.store_id` | `string` | The store ID that was sent as part of the request | +| `fga-client.request.model_id` | `string` | The authorization model ID that was sent as part of the request, if any | +| `fga-client.request.client_id` | `string` | The client ID associated with the request, if any | +| `fga-client.user` | `string` | The user that is associated with the action of the request for check and list users | +| `http.status_code ` | `int` | The status code of the response | +| `http.method` | `string` | The HTTP method for the request | +| `http.host` | `string` | Host identifier of the origin the request was sent to | \ No newline at end of file