diff --git a/backend/src/routes/api/connection-types/connectionTypeUtils.ts b/backend/src/routes/api/connection-types/connectionTypeUtils.ts new file mode 100644 index 0000000000..a3d2110ae8 --- /dev/null +++ b/backend/src/routes/api/connection-types/connectionTypeUtils.ts @@ -0,0 +1,190 @@ +import { PatchUtils, V1ConfigMap } from '@kubernetes/client-node'; +import { KnownLabels, KubeFastifyInstance, RecursivePartial } from '../../../types'; +import { getNamespaces } from '../../../utils/notebookUtils'; +import { errorHandler } from '../../../utils'; + +const isConnectionTypeConfigMap = (configMap: V1ConfigMap): boolean => + configMap.metadata.labels && + configMap.metadata.labels[KnownLabels.DASHBOARD_RESOURCE] === 'true' && + configMap.metadata.labels[KnownLabels.CONNECTION_TYPE] === 'true'; + +const isExistingConnectionType = async ( + fastify: KubeFastifyInstance, + name: string, +): Promise => { + const coreV1Api = fastify.kube.coreV1Api; + const { dashboardNamespace } = getNamespaces(fastify); + + const response = await coreV1Api.readNamespacedConfigMap(name, dashboardNamespace); + return isConnectionTypeConfigMap(response.body); +}; + +export const listConnectionTypes = async (fastify: KubeFastifyInstance): Promise => { + const { dashboardNamespace } = getNamespaces(fastify); + const coreV1Api = fastify.kube.coreV1Api; + const connectionTypes: V1ConfigMap[] = []; + + let _continue: string = undefined; + let remainingItemCount = 1; + try { + while (remainingItemCount) { + const response = await coreV1Api.listNamespacedConfigMap( + dashboardNamespace, + undefined, + undefined, + _continue, + undefined, + `${KnownLabels.DASHBOARD_RESOURCE} = true, ${KnownLabels.CONNECTION_TYPE} = true`, + ); + connectionTypes.push(...(response?.body?.items ?? [])); + remainingItemCount = response?.body.metadata?.remainingItemCount; + _continue = response?.body.metadata?._continue; + } + return connectionTypes; + } catch (e) { + fastify.log.error(`Error fetching configmaps for connection types: `, e); + throw new Error(`Failed to list connection types: ${errorHandler(e)}.`); + } +}; + +export const getConnectionType = async ( + fastify: KubeFastifyInstance, + name: string, +): Promise => { + const { dashboardNamespace } = getNamespaces(fastify); + const coreV1Api = fastify.kube.coreV1Api; + try { + const response = await coreV1Api.readNamespacedConfigMap(name, dashboardNamespace); + if (!isConnectionTypeConfigMap(response.body)) { + throw new Error(`object is not a connection type.`); + } + return response.body; + } catch (e) { + fastify.log.error(`Error fetching connection type: `, e); + throw new Error(`Failed to get connection type: ${errorHandler(e)}.`); + } +}; + +export const createConnectionType = async ( + fastify: KubeFastifyInstance, + connectionType: V1ConfigMap, +): Promise<{ success: boolean; error: string }> => { + const coreV1Api = fastify.kube.coreV1Api; + const { dashboardNamespace } = getNamespaces(fastify); + + if (!isConnectionTypeConfigMap(connectionType)) { + const error = 'Unable to add connection type, incorrect labels.'; + fastify.log.error(error); + return { success: false, error }; + } + + try { + await coreV1Api.createNamespacedConfigMap(dashboardNamespace, connectionType); + return { success: true, error: '' }; + } catch (e) { + const error = `Unable to add connection type: ${errorHandler(e)}.`; + fastify.log.error(error); + return { success: false, error }; + } +}; + +export const updateConnectionType = async ( + fastify: KubeFastifyInstance, + name: string, + connectionType: V1ConfigMap, +): Promise<{ success: boolean; error: string }> => { + const coreV1Api = fastify.kube.coreV1Api; + const { dashboardNamespace } = getNamespaces(fastify); + + if (!isConnectionTypeConfigMap(connectionType)) { + const error = 'Unable to add connection type, incorrect labels.'; + fastify.log.error(error); + return { success: false, error }; + } + + try { + const validConnectionType = await isExistingConnectionType(fastify, name); + if (!validConnectionType) { + const error = `Unable to update connection type, object is not a connection type`; + fastify.log.error(error); + return { success: false, error }; + } + + await coreV1Api.replaceNamespacedConfigMap(name, dashboardNamespace, connectionType); + return { success: true, error: '' }; + } catch (e) { + const error = `Unable to update connection type: ${errorHandler(e)}.`; + fastify.log.error(error); + return { success: false, error }; + } +}; + +export const patchConnectionType = async ( + fastify: KubeFastifyInstance, + name: string, + partialConfigMap: RecursivePartial, +): Promise<{ success: boolean; error: string }> => { + const coreV1Api = fastify.kube.coreV1Api; + const { dashboardNamespace } = getNamespaces(fastify); + + if ( + (partialConfigMap.metadata.labels?.[KnownLabels.DASHBOARD_RESOURCE] && + partialConfigMap.metadata.labels[KnownLabels.DASHBOARD_RESOURCE] !== 'true') || + (partialConfigMap.metadata.labels?.[KnownLabels.CONNECTION_TYPE] && + partialConfigMap.metadata.labels[KnownLabels.CONNECTION_TYPE] !== 'true') + ) { + const error = 'Unable to update connection type, incorrect labels.'; + fastify.log.error(error); + return { success: false, error }; + } + + try { + const validConnectionType = await isExistingConnectionType(fastify, name); + if (!validConnectionType) { + const error = `Unable to update connection type, object is not a connection type`; + fastify.log.error(error); + return { success: false, error }; + } + const options = { + headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_PATCH }, + }; + + await coreV1Api.patchNamespacedConfigMap( + name, + dashboardNamespace, + partialConfigMap, + undefined, + undefined, + undefined, + undefined, + options, + ); + return { success: true, error: '' }; + } catch (e) { + const error = `Unable to update connection type: ${errorHandler(e)}.`; + fastify.log.error(error); + return { success: false, error }; + } +}; + +export const deleteConnectionType = async ( + fastify: KubeFastifyInstance, + name: string, +): Promise<{ success: boolean; error: string }> => { + const { dashboardNamespace } = getNamespaces(fastify); + const coreV1Api = fastify.kube.coreV1Api; + try { + const validConnectionType = await isExistingConnectionType(fastify, name); + if (!validConnectionType) { + const error = `Unable to delete connection type, object is not a connection type`; + fastify.log.error(error); + return { success: false, error }; + } + await coreV1Api.deleteNamespacedConfigMap(name, dashboardNamespace); + return { success: true, error: '' }; + } catch (e) { + const error = `Unable to delete connection type: ${errorHandler(e)}.`; + fastify.log.error(error); + return { success: false, error }; + } +}; diff --git a/backend/src/routes/api/connection-types/index.ts b/backend/src/routes/api/connection-types/index.ts new file mode 100644 index 0000000000..9dc4d1e05d --- /dev/null +++ b/backend/src/routes/api/connection-types/index.ts @@ -0,0 +1,91 @@ +import { V1ConfigMap } from '@kubernetes/client-node'; +import { FastifyReply, FastifyRequest } from 'fastify'; +import { KubeFastifyInstance, RecursivePartial } from '../../../types'; +import { secureAdminRoute } from '../../../utils/route-security'; +import { + getConnectionType, + listConnectionTypes, + createConnectionType, + updateConnectionType, + patchConnectionType, + deleteConnectionType, +} from './connectionTypeUtils'; + +module.exports = async (fastify: KubeFastifyInstance) => { + fastify.get( + '/', + secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => + listConnectionTypes(fastify) + .then((res) => res) + .catch((res) => { + reply.send(res); + }), + ), + ); + + fastify.get( + '/:name', + secureAdminRoute(fastify)( + async (request: FastifyRequest<{ Params: { name: string } }>, reply: FastifyReply) => + getConnectionType(fastify, request.params.name) + .then((res) => res) + .catch((res) => { + reply.send(res); + }), + ), + ); + + fastify.post( + '/', + secureAdminRoute(fastify)( + async (request: FastifyRequest<{ Body: V1ConfigMap }>, reply: FastifyReply) => + createConnectionType(fastify, request.body) + .then((res) => res) + .catch((res) => { + reply.send(res); + }), + ), + ); + + fastify.put( + '/:name', + secureAdminRoute(fastify)( + async ( + request: FastifyRequest<{ Params: { name: string }; Body: V1ConfigMap }>, + reply: FastifyReply, + ) => + updateConnectionType(fastify, request.params.name, request.body) + .then((res) => res) + .catch((res) => { + reply.send(res); + }), + ), + ); + + fastify.patch( + '/:name', + secureAdminRoute(fastify)( + async ( + request: FastifyRequest<{ Params: { name: string }; Body: RecursivePartial }>, + reply: FastifyReply, + ) => + patchConnectionType(fastify, request.params.name, request.body) + .then((res) => res) + .catch((res) => { + reply.send(res); + }), + ), + ); + + fastify.delete( + '/:name', + secureAdminRoute(fastify)( + async (request: FastifyRequest<{ Params: { name: string } }>, reply: FastifyReply) => + deleteConnectionType(fastify, request.params.name) + .then((res) => res) + .catch((res) => { + reply.send(res); + }), + ), + ); +}; diff --git a/backend/src/types.ts b/backend/src/types.ts index 0da7f13d37..28249c0e84 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -981,6 +981,7 @@ export enum KnownLabels { PROJECT_SHARING = 'opendatahub.io/project-sharing', MODEL_SERVING_PROJECT = 'modelmesh-enabled', DATA_CONNECTION_AWS = 'opendatahub.io/managed', + CONNECTION_TYPE = 'opendatahub.io/connection-type', } type ComponentNames = diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 53ac2b02a2..6088e382bd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,6 +26,7 @@ "@patternfly/react-tokens": "^5.3.1", "@patternfly/react-topology": "^5.4.0-prerelease.10", "@patternfly/react-virtualized-extension": "^5.1.0", + "@segment/analytics-next": "^1.72.0", "@types/classnames": "^2.3.1", "axios": "^1.6.4", "classnames": "^2.2.6", @@ -3716,6 +3717,27 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@monaco-editor/loader": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz", @@ -4211,6 +4233,87 @@ "node": ">=14.0.0" } }, + "node_modules/@segment/analytics-core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.6.0.tgz", + "integrity": "sha512-bn9X++IScUfpT7aJGjKU/yJAu/Ko2sYD6HsKA70Z2560E89x30pqgqboVKY8kootvQnT4UKCJiUr5NDMgjmWdQ==", + "license": "MIT", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-generic-utils": "1.2.0", + "dset": "^3.1.2", + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-generic-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.2.0.tgz", + "integrity": "sha512-DfnW6mW3YQOLlDQQdR89k4EqfHb0g/3XvBXkovH1FstUN93eL1kfW9CsDcVQyH3bAC5ZsFyjA/o/1Q2j0QeoWw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-next": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-next/-/analytics-next-1.72.0.tgz", + "integrity": "sha512-NqJ8819q1DJ2nWo71XnJhkdBYIh0oYOP8yBqJhcyqsQYiSo3Eg4NGLm+7ubMcFzB/1YRM005medyvokEmDocuQ==", + "license": "MIT", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-core": "1.6.0", + "@segment/analytics-generic-utils": "1.2.0", + "@segment/analytics.js-video-plugins": "^0.2.1", + "@segment/facade": "^3.4.9", + "dset": "^3.1.2", + "js-cookie": "3.0.1", + "node-fetch": "^2.6.7", + "tslib": "^2.4.1", + "unfetch": "^4.1.0" + } + }, + "node_modules/@segment/analytics.js-video-plugins": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@segment/analytics.js-video-plugins/-/analytics.js-video-plugins-0.2.1.tgz", + "integrity": "sha512-lZwCyEXT4aaHBLNK433okEKdxGAuyrVmop4BpQqQSJuRz0DglPZgd9B/XjiiWs1UyOankg2aNYMN3VcS8t4eSQ==", + "license": "ISC", + "dependencies": { + "unfetch": "^3.1.1" + } + }, + "node_modules/@segment/analytics.js-video-plugins/node_modules/unfetch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-3.1.2.tgz", + "integrity": "sha512-L0qrK7ZeAudGiKYw6nzFjnJ2D5WHblUBwmHIqtPS6oKUd+Hcpk7/hKsSmcHsTlpd1TbTNsiRBUKRq3bHLNIqIw==", + "license": "MIT" + }, + "node_modules/@segment/facade": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/@segment/facade/-/facade-3.4.10.tgz", + "integrity": "sha512-xVQBbB/lNvk/u8+ey0kC/+g8pT3l0gCT8O2y9Z+StMMn3KAFAQ9w8xfgef67tJybktOKKU7pQGRPolRM1i1pdA==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@segment/isodate-traverse": "^1.1.1", + "inherits": "^2.0.4", + "new-date": "^1.0.3", + "obj-case": "0.2.1" + } + }, + "node_modules/@segment/isodate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@segment/isodate/-/isodate-1.0.3.tgz", + "integrity": "sha512-BtanDuvJqnACFkeeYje7pWULVv8RgZaqKHWwGFnL/g/TH/CcZjkIVTfGDp/MAxmilYHUkrX70SqwnYSTNEaN7A==", + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@segment/isodate-traverse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@segment/isodate-traverse/-/isodate-traverse-1.1.1.tgz", + "integrity": "sha512-+G6e1SgAUkcq0EDMi+SRLfT48TNlLPF3QnSgFGVs0V9F3o3fq/woQ2rHFlW20W0yy5NnCUH0QGU3Am2rZy/E3w==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@segment/isodate": "^1.0.3" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -9798,6 +9901,15 @@ "webpack": "^1 || ^2 || ^3 || ^4 || ^5" } }, + "node_modules/dset": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.3.tgz", + "integrity": "sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -12639,8 +12751,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -16091,6 +16202,15 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18025,6 +18145,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "devOptional": true }, + "node_modules/new-date": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/new-date/-/new-date-1.0.3.tgz", + "integrity": "sha512-0fsVvQPbo2I18DT2zVHpezmeeNYV2JaJSrseiHLc17GNOxJzUdx5mvSigPu8LtIfZSij5i1wXnXFspEs2CD6hA==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@segment/isodate": "1.0.3" + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -18041,6 +18170,48 @@ "tslib": "^2.0.3" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -18471,6 +18642,12 @@ "node": ">=8" } }, + "node_modules/obj-case": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/obj-case/-/obj-case-0.2.1.tgz", + "integrity": "sha512-PquYBBTy+Y6Ob/O2574XHhDtHJlV1cJHMCgW+rDRc9J5hhmRelJB3k5dTK/3cVmFVtzvAKuENeuLpoyTzMzkOg==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -22639,6 +22816,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==", + "license": "MIT" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9d39321c55..2adb953921 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -69,6 +69,7 @@ "@patternfly/react-tokens": "^5.3.1", "@patternfly/react-topology": "^5.4.0-prerelease.10", "@patternfly/react-virtualized-extension": "^5.1.0", + "@segment/analytics-next": "^1.72.0", "@types/classnames": "^2.3.1", "axios": "^1.6.4", "classnames": "^2.2.6", diff --git a/frontend/src/concepts/analyticsTracking/initSegment.tsx b/frontend/src/concepts/analyticsTracking/initSegment.tsx index c464dfea98..ce53aba158 100644 --- a/frontend/src/concepts/analyticsTracking/initSegment.tsx +++ b/frontend/src/concepts/analyticsTracking/initSegment.tsx @@ -1,74 +1,26 @@ +import { AnalyticsBrowser } from '@segment/analytics-next'; + export const initSegment = async (props: { segmentKey: string; enabled: boolean; }): Promise => { const { segmentKey, enabled } = props; - const analytics = (window.analytics = window.analytics || []); - if (analytics.initialize) { + if (!enabled || !segmentKey) { return; } - if (analytics.invoked) { - /* eslint-disable-next-line no-console */ - console.error('Segment snippet included twice.'); - } else { - analytics.invoked = true; - analytics.methods = [ - 'trackSubmit', - 'trackClick', - 'trackLink', - 'trackForm', - 'pageview', - 'identify', - 'reset', - 'group', - 'track', - 'ready', - 'alias', - 'debug', - 'page', - 'once', - 'off', - 'on', - 'addSourceMiddleware', - 'addIntegrationMiddleware', - 'setAnonymousId', - 'addDestinationMiddleware', - ]; - analytics.factory = - (e: string) => - (...t: unknown[]) => { - t.unshift(e); - analytics.push(t); - return analytics; - }; - for (let e = 0; e < analytics.methods.length; e++) { - const key = analytics.methods[e]; - analytics[key] = analytics.factory(key); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - analytics.load = (key: string, options: any) => { - const t = document.createElement('script'); - t.type = 'text/javascript'; - t.async = true; - t.src = `https://console.redhat.com/connections/cdn/analytics.js/v1/${encodeURIComponent( - key, - )}/analytics.min.js`; - const n = document.getElementsByTagName('script')[0]; - if (n.parentNode) { - n.parentNode.insertBefore(t, n); - } - analytics._loadOptions = options; - }; - analytics.SNIPPET_VERSION = '4.13.1'; - if (segmentKey && enabled) { - analytics.load(segmentKey, { - integrations: { - 'Segment.io': { - apiHost: 'console.redhat.com/connections/api/v1', - protocol: 'https', - }, + window.analytics = AnalyticsBrowser.load( + { + writeKey: segmentKey, + cdnURL: 'https://console.redhat.com/connections/cdn', + }, + + { + integrations: { + 'Segment.io': { + apiHost: 'console.redhat.com/connections/api/v1', + protocol: 'https', }, - }); - } - } + }, + }, + ); }; diff --git a/frontend/src/concepts/connectionTypes/__tests__/utils.spec.ts b/frontend/src/concepts/connectionTypes/__tests__/utils.spec.ts index cbb9557561..93f7aacc55 100644 --- a/frontend/src/concepts/connectionTypes/__tests__/utils.spec.ts +++ b/frontend/src/concepts/connectionTypes/__tests__/utils.spec.ts @@ -8,14 +8,14 @@ describe('utils', () => { it('should serialize / deserialize connection type fields', () => { const ct = mockConnectionTypeConfigMapObj({}); const configMap = toConnectionTypeConfigMap(ct); - expect(typeof configMap.data.fields).toBe('string'); + expect(typeof configMap.data?.fields).toBe('string'); expect(ct).toEqual(toConnectionTypeConfigMapObj(toConnectionTypeConfigMap(ct))); }); it('should serialize / deserialize connection type with missing fields', () => { const ct = mockConnectionTypeConfigMapObj({ fields: undefined }); const configMap = toConnectionTypeConfigMap(ct); - expect(configMap.data.fields).toBeUndefined(); + expect(configMap.data?.fields).toBeUndefined(); expect(ct).toEqual(toConnectionTypeConfigMapObj(configMap)); }); }); diff --git a/frontend/src/concepts/connectionTypes/types.ts b/frontend/src/concepts/connectionTypes/types.ts index 289643f57d..82a5b959d5 100644 --- a/frontend/src/concepts/connectionTypes/types.ts +++ b/frontend/src/concepts/connectionTypes/types.ts @@ -95,14 +95,14 @@ export type ConnectionTypeConfigMap = K8sResourceCommon & { 'opendatahub.io/connection-type': 'true'; }; }; - data: { + data?: { // JSON of type ConnectionTypeField fields?: string; }; }; export type ConnectionTypeConfigMapObj = Omit & { - data: { + data?: { fields?: ConnectionTypeField[]; }; }; diff --git a/frontend/src/concepts/connectionTypes/utils.ts b/frontend/src/concepts/connectionTypes/utils.ts index 7fb9121a2f..dac5355724 100644 --- a/frontend/src/concepts/connectionTypes/utils.ts +++ b/frontend/src/concepts/connectionTypes/utils.ts @@ -7,12 +7,16 @@ export const toConnectionTypeConfigMapObj = ( configMap: ConnectionTypeConfigMap, ): ConnectionTypeConfigMapObj => ({ ...configMap, - data: { fields: configMap.data.fields ? JSON.parse(configMap.data.fields) : undefined }, + data: configMap.data + ? { fields: configMap.data.fields ? JSON.parse(configMap.data.fields) : undefined } + : undefined, }); export const toConnectionTypeConfigMap = ( obj: ConnectionTypeConfigMapObj, ): ConnectionTypeConfigMap => ({ ...obj, - data: { fields: obj.data.fields ? JSON.stringify(obj.data.fields) : undefined }, + data: obj.data + ? { fields: obj.data.fields ? JSON.stringify(obj.data.fields) : undefined } + : undefined, }); diff --git a/frontend/src/services/connectionTypesService.ts b/frontend/src/services/connectionTypesService.ts new file mode 100644 index 0000000000..d4e9bcf63e --- /dev/null +++ b/frontend/src/services/connectionTypesService.ts @@ -0,0 +1,85 @@ +import axios from '~/utilities/axios'; +import { ResponseStatus } from '~/types'; +import { + ConnectionTypeConfigMap, + ConnectionTypeConfigMapObj, +} from '~/concepts/connectionTypes/types'; +import { + toConnectionTypeConfigMap, + toConnectionTypeConfigMapObj, +} from '~/concepts/connectionTypes/utils'; + +export const fetchConnectionTypes = (): Promise => { + const url = `/api/connection-types`; + return axios + .get(url) + .then((response) => + response.data.map((cm: ConnectionTypeConfigMap) => toConnectionTypeConfigMapObj(cm)), + ) + .catch((e) => { + throw new Error(e.response.data.message); + }); +}; + +export const fetchConnectionType = (name: string): Promise => { + const url = `/api/connection-types/${name}`; + return axios + .get(url) + .then((response) => toConnectionTypeConfigMapObj(response.data)) + .catch((e) => { + throw new Error(e.response.data.message); + }); +}; + +export const createConnectionType = ( + connectionType: ConnectionTypeConfigMapObj, +): Promise => { + const url = `/api/connection-types`; + return axios + .post(url, toConnectionTypeConfigMap(connectionType)) + .then((response) => response.data) + .catch((e) => { + throw new Error(e.response.data.message); + }); +}; + +export const updateConnectionType = ( + connectionType: ConnectionTypeConfigMapObj, +): Promise => { + const url = `/api/connection-types/${connectionType.metadata.name}`; + return axios + .put(url, toConnectionTypeConfigMap(connectionType)) + .then((response) => response.data) + .catch((e) => { + throw new Error(e.response.data.message); + }); +}; + +export const updateConnectionTypeEnabled = ( + name: string, + enabled: boolean, +): Promise => { + const url = `/api/connection-types/${name}`; + return axios + .patch(url, [ + { + op: 'replace', + path: '/metadata/annotations/opendatahub.io~1enabled', + value: enabled, + }, + ]) + .then((response) => response.data) + .catch((e) => { + throw new Error(e.response.data.message); + }); +}; + +export const deleteConnectionType = (name: string): Promise => { + const url = `/api/connection-types/${name}`; + return axios + .delete(url) + .then((response) => response.data) + .catch((e) => { + throw new Error(e.response.data.message); + }); +};