From 76e7d85a48df560b25ba946e734cff35f8b6779f Mon Sep 17 00:00:00 2001 From: Quentin AUBERT Date: Thu, 5 Dec 2024 16:38:16 +0100 Subject: [PATCH] FIX #780 --- daikoku/app/controllers/ApiController.scala | 7 +- daikoku/app/domain/apikeyEntities.scala | 3 +- daikoku/app/domain/json.scala | 7 +- daikoku/app/utils/ApiService.scala | 23 +-- .../backoffice/apikeys/TeamApiKeysForApi.tsx | 1 - .../src/components/frontend/api/ApiHome.tsx | 5 +- .../components/frontend/api/ApiPricing.tsx | 131 ++++++++++-------- .../frontend/fastMode/FastApiCard.tsx | 114 +++++++++------ .../frontend/fastMode/FastItemView.tsx | 2 +- .../javascript/src/contexts/modalContext.tsx | 1 - .../src/locales/en/translation.json | 3 +- .../src/locales/fr/translation.json | 3 +- daikoku/javascript/src/services/index.ts | 5 +- daikoku/javascript/src/types/team.ts | 1 + 14 files changed, 189 insertions(+), 117 deletions(-) diff --git a/daikoku/app/controllers/ApiController.scala b/daikoku/app/controllers/ApiController.scala index 091e2ac3f..b16985638 100644 --- a/daikoku/app/controllers/ApiController.scala +++ b/daikoku/app/controllers/ApiController.scala @@ -1250,7 +1250,8 @@ class ApiController( customReadOnly = customReadOnly, adminCustomName = adminCustomName, motivation = motivation, - parentSubscriptionId = Some(ApiSubscriptionId(apiKeyId)) + parentSubscriptionId = Some(ApiSubscriptionId(apiKeyId)), + customName = None ) } } @@ -1267,6 +1268,7 @@ class ApiController( implicit val language: String = ctx.request.getLanguage(ctx.tenant) implicit val currentUser: User = ctx.user + val customName = ctx.request.body.getBodyField[String]("customName") val motivation = ctx.request.body.getBodyField[JsObject]("motivation") val customMaxPerSecond = ctx.request.body.getBodyField[Long]("customMaxPerSecond") @@ -1292,7 +1294,8 @@ class ApiController( customMaxPerMonth = customMaxPerMonth, customReadOnly = customReadOnly, adminCustomName = adminCustomName, - motivation = motivation + motivation = motivation, + customName = customName ) } } diff --git a/daikoku/app/domain/apikeyEntities.scala b/daikoku/app/domain/apikeyEntities.scala index cca9d40ae..7dab6554e 100644 --- a/daikoku/app/domain/apikeyEntities.scala +++ b/daikoku/app/domain/apikeyEntities.scala @@ -307,7 +307,8 @@ case class SubscriptionDemand( customMaxPerSecond: Option[Long] = None, customMaxPerDay: Option[Long] = None, customMaxPerMonth: Option[Long] = None, - adminCustomName: Option[String] = None + adminCustomName: Option[String] = None, + customName: Option[String] = None ) extends CanJson[SubscriptionDemand] { override def asJson: JsValue = json.SubscriptionDemandFormat.writes(this) } diff --git a/daikoku/app/domain/json.scala b/daikoku/app/domain/json.scala index fa24a19bd..77b827ca1 100644 --- a/daikoku/app/domain/json.scala +++ b/daikoku/app/domain/json.scala @@ -2918,6 +2918,10 @@ object json { .getOrElse(JsNull) .as[JsValue], "adminCustomName" -> o.adminCustomName + .map(JsString.apply) + .getOrElse(JsNull) + .as[JsValue], + "customName" -> o.customName .map(JsString.apply) .getOrElse(JsNull) .as[JsValue] @@ -2948,7 +2952,8 @@ object json { customMaxPerDay = (json \ "customMaxPerDay").asOpt[Long], customMaxPerMonth = (json \ "customMaxPerMonth").asOpt[Long], customReadOnly = (json \ "customReadOnly").asOpt[Boolean], - adminCustomName = (json \ "adminCustomName").asOpt[String] + adminCustomName = (json \ "adminCustomName").asOpt[String], + customName = (json \ "customName").asOpt[String] ) ) } recover { diff --git a/daikoku/app/utils/ApiService.scala b/daikoku/app/utils/ApiService.scala index 76a1640af..2b791307a 100644 --- a/daikoku/app/utils/ApiService.scala +++ b/daikoku/app/utils/ApiService.scala @@ -199,7 +199,8 @@ class ApiService( adminCustomName: Option[String] = None, thirdPartySubscriptionInformations: Option[ ThirdPartySubscriptionInformations - ] + ], + customName: Option[String] ): Future[Either[AppError, ApiSubscription]] = { def createKey( api: Api, @@ -262,7 +263,7 @@ class ApiService( team = team.id, api = api.id, by = user.id, - customName = None, + customName = customName, rotation = plan.autoRotation.map(rotation => ApiSubscriptionRotation(enabled = rotation) ), @@ -2163,7 +2164,8 @@ class ApiService( customReadOnly = demand.customReadOnly, adminCustomName = demand.adminCustomName, thirdPartySubscriptionInformations = - maybeSubscriptionInformations + maybeSubscriptionInformations, + customName = demand.customName ) ) administrators <- EitherT.liftF( @@ -2253,7 +2255,8 @@ class ApiService( customMaxPerDay: Option[Long], customMaxPerMonth: Option[Long], customReadOnly: Option[Boolean], - adminCustomName: Option[String] + adminCustomName: Option[String], + customName: Option[String] )(implicit language: String, currentUser: User) = { import cats.implicits._ @@ -2411,7 +2414,8 @@ class ApiService( customMaxPerDay, customMaxPerMonth, customReadOnly, - adminCustomName + adminCustomName, + customName ) } yield result @@ -2431,7 +2435,8 @@ class ApiService( customMaxPerDay: Option[Long], customMaxPerMonth: Option[Long], customReadOnly: Option[Boolean], - adminCustomName: Option[String] + adminCustomName: Option[String], + customName: Option[String] )(implicit language: String): EitherT[Future, AppError, Result] = { import cats.implicits._ @@ -2457,7 +2462,8 @@ class ApiService( plan, team, apiKeyId, - thirdPartySubscriptionInformations = None + thirdPartySubscriptionInformations = None, + customName = customName ) ).map(s => Ok( @@ -2493,7 +2499,8 @@ class ApiService( customMaxPerDay = customMaxPerDay, customMaxPerMonth = customMaxPerMonth, customReadOnly = customReadOnly, - adminCustomName = adminCustomName + adminCustomName = adminCustomName, + customName = customName ) ) ) diff --git a/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeysForApi.tsx b/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeysForApi.tsx index ea5e47a17..3e64a81e4 100644 --- a/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeysForApi.tsx +++ b/daikoku/javascript/src/components/backoffice/apikeys/TeamApiKeysForApi.tsx @@ -143,7 +143,6 @@ export const TeamApiKeysForApi = () => { }; const toggleApiKey = (subscription: ISubscription) => { - console.debug("toggle") return Services.archiveApiKey( currentTeam._id, subscription._id, diff --git a/daikoku/javascript/src/components/frontend/api/ApiHome.tsx b/daikoku/javascript/src/components/frontend/api/ApiHome.tsx index 84950450e..f4df1a388 100644 --- a/daikoku/javascript/src/components/frontend/api/ApiHome.tsx +++ b/daikoku/javascript/src/components/frontend/api/ApiHome.tsx @@ -243,14 +243,15 @@ export const ApiHome = ({ ); }; - const askForApikeys = ({ team, plan, apiKey, motivation }: { team: string, plan: IUsagePlan, apiKey?: ISubscription, motivation?: object }) => { + const askForApikeys = ({ team, plan, apiKey, motivation, customName }: + { team: string, plan: IUsagePlan, apiKey?: ISubscription, motivation?: object, customName?: string }) => { const planName = formatPlanType(plan, translate); if (api) { return ( apiKey ? Services.extendApiKey(api._id, apiKey._id, team, plan._id, motivation) - : Services.askForApiKey(api._id, team, plan._id, motivation) + : Services.askForApiKey(api._id, team, plan._id, motivation, customName) ).then((result) => { if (isError(result)) { diff --git a/daikoku/javascript/src/components/frontend/api/ApiPricing.tsx b/daikoku/javascript/src/components/frontend/api/ApiPricing.tsx index b8f3a6ff8..2be502266 100644 --- a/daikoku/javascript/src/components/frontend/api/ApiPricing.tsx +++ b/daikoku/javascript/src/components/frontend/api/ApiPricing.tsx @@ -3,7 +3,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import classNames from 'classnames'; import difference from 'lodash/difference'; import find from 'lodash/find'; -import React, { useContext, useEffect } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Link, useMatch, useNavigate } from 'react-router-dom'; import { I18nContext, ModalContext } from '../../../contexts'; @@ -38,6 +38,7 @@ import { ApiDocumentation } from './ApiDocumentation'; import { ApiRedoc } from './ApiRedoc'; import { ApiSwagger } from './ApiSwagger'; import { ApiKeyCard } from '../../backoffice/apikeys/TeamApiKeysForApi'; +import { Form, type } from '@maif/react-forms'; export const currency = (plan?: IBaseUsagePlan) => { if (!plan) { @@ -55,6 +56,7 @@ type ApiPricingCardProps = { plan: IUsagePlan; apiKey?: ISubscription; motivation?: object; + customName?: string; }) => Promise; myTeams: Array; ownerTeam: ITeamSimple; @@ -69,14 +71,13 @@ const ApiPricingCard = (props: ApiPricingCardProps) => { openLoginOrRegisterModal, openApiKeySelectModal, openCustomModal, - close, - openRightPanel + close } = useContext(ModalContext); const { client } = useContext(getApolloContext()); const { connectedUser, tenant } = useContext(GlobalContext); - const showApiKeySelectModal = (team: string) => { + const showApiKeySelectModal = (team: string, customName?: string) => { const { plan } = props; //FIXME: not bwaaagh !! @@ -87,6 +88,7 @@ const ApiPricingCard = (props: ApiPricingCardProps) => { const askForApikeys = ( team: string, plan: IUsagePlan, + customName?: string, apiKey?: ISubscription ) => { const adminStep = plan.subscriptionProcess.find((s) => @@ -97,12 +99,12 @@ const ApiPricingCard = (props: ApiPricingCardProps) => { title: translate('motivations.modal.title'), schema: adminStep.schema, onSubmit: (motivation: object) => - props.askForApikeys({ team, plan, apiKey, motivation }), + props.askForApikeys({ team, plan, apiKey, motivation, customName }), actionLabel: translate('Send'), value: apiKey?.customMetadata, }); } else { - props.askForApikeys({ team, plan: plan, apiKey }).then(() => close()); + props.askForApikeys({ team, plan: plan, apiKey, customName }).then(() => close()); } }; @@ -161,14 +163,14 @@ const ApiPricingCard = (props: ApiPricingCardProps) => { !tenant.aggregationApiKeysSecurity || !plan.aggregationApiKeysSecurity || filteredApiKeys.length <= 0 ) { - askForApikeys(team, plan); + askForApikeys(team, plan, customName); } else { openApiKeySelectModal({ plan, apiKeys: filteredApiKeys, - onSubscribe: () => askForApikeys(team, plan), + onSubscribe: () => askForApikeys(team, plan, customName), extendApiKey: (apiKey: ISubscription) => - askForApikeys(team, plan, apiKey), + askForApikeys(team, plan, customName, apiKey), }); } } @@ -229,50 +231,50 @@ const ApiPricingCard = (props: ApiPricingCardProps) => { }) } -// const displaySubscription = () => { -// Services.getMySubscriptions(props.api._id, props.api.currentVersion) -// .then(r => { -// openRightPanel({ -// title: "test", -// content:
-// {r.subscriptions.map(subscription => { -// return ( -// Promise.resolve()} -// toggle={console.debug} -// makeUniqueApiKey={console.debug} -// deleteApiKey={console.debug} -// toggleRotation={( -// plan, -// enabled, -// rotationEvery, -// gracePeriod -// ) => -// Promise.resolve() -// } -// regenerateSecret={console.debug} -// transferKey={console.debug} -// /> -// ) -// })} -//
-// }) -// }) -// } + // const displaySubscription = () => { + // Services.getMySubscriptions(props.api._id, props.api.currentVersion) + // .then(r => { + // openRightPanel({ + // title: "test", + // content:
+ // {r.subscriptions.map(subscription => { + // return ( + // Promise.resolve()} + // toggle={console.debug} + // makeUniqueApiKey={console.debug} + // deleteApiKey={console.debug} + // toggleRotation={( + // plan, + // enabled, + // rotationEvery, + // gracePeriod + // ) => + // Promise.resolve() + // } + // regenerateSecret={console.debug} + // transferKey={console.debug} + // /> + // ) + // })} + //
+ // }) + // }) + // } return (
; acceptedTeams: Array; allowMultipleDemand?: boolean; - showApiKeySelectModal: (teamId: string) => void; + showApiKeySelectModal: (teamId: string, customName?: string) => void; plan: IUsagePlan; }; const TeamSelector = (props: ITeamSelector) => { + const [customName, setCustomName] = useState(); + const { translate } = useContext(I18nContext); const { close } = useContext(ModalContext); const navigate = useNavigate(); @@ -481,7 +485,7 @@ const TeamSelector = (props: ITeamSelector) => { })} onClick={() => { return allowed - ? props.showApiKeySelectModal(team._id) + ? props.showApiKeySelectModal(team._id, customName) : () => { }; }} > @@ -510,6 +514,22 @@ const TeamSelector = (props: ITeamSelector) => { ); })}
+
+ {translate("api.pricing.subscription.custom.name.help")} +
+
setCustomName(data.customName)} + options={{ + autosubmit: true, + actions: { submit: { display: false } } + }} + /> ); @@ -526,6 +546,7 @@ type ApiPricingProps = { plan: IUsagePlan; apiKey?: ISubscription; motivation?: object; + customName?: string; }) => Promise; }; diff --git a/daikoku/javascript/src/components/frontend/fastMode/FastApiCard.tsx b/daikoku/javascript/src/components/frontend/fastMode/FastApiCard.tsx index 1a9c57f81..86119a253 100644 --- a/daikoku/javascript/src/components/frontend/fastMode/FastApiCard.tsx +++ b/daikoku/javascript/src/components/frontend/fastMode/FastApiCard.tsx @@ -19,6 +19,7 @@ import { } from '../../../types'; import { isSubscriptionProcessIsAutomatic, Option } from '../../utils'; import { GlobalContext } from '../../../contexts/globalContext'; +import { type } from '@maif/react-forms'; type FastApiCardProps = { team: ITeamSimple; @@ -72,18 +73,19 @@ export const FastApiCard = (props: FastApiCardProps) => { apiId: string, team: ITeamSimple, plan: IFastPlan, + customName?: string, apiKey?: ISubscription ) => { const apiKeyDemand = (motivation?: object) => apiKey ? Services.extendApiKey( - apiId, - apiKey._id, - team._id, - plan._id, - motivation - ) - : Services.askForApiKey(apiId, team._id, plan._id, motivation); + apiId, + apiKey._id, + team._id, + plan._id, + motivation + ) + : Services.askForApiKey(apiId, team._id, plan._id, motivation, customName); const adminStep = plan.subscriptionProcess.find((s) => isValidationStepTeamAdmin(s) @@ -93,19 +95,20 @@ export const FastApiCard = (props: FastApiCardProps) => { title: translate('motivations.modal.title'), schema: adminStep.schema, onSubmit: (motivation) => { - apiKeyDemand(motivation).then((response) => { - if (isError(response)) { - toast.error(response.error); - } else { - toast.info( - translate({ - key: 'subscription.plan.waiting', - replacements: [plan.customName!, team.name], - }) - ); - queryClient.invalidateQueries({ queryKey: ['data'] }); - } - }); + apiKeyDemand(motivation) + .then((response) => { + if (isError(response)) { + toast.error(response.error); + } else { + toast.info( + translate({ + key: 'subscription.plan.waiting', + replacements: [plan.customName!, team.name], + }) + ); + queryClient.invalidateQueries({ queryKey: ['data'] }); + } + }); }, actionLabel: translate('Send'), value: apiKey?.customMetadata @@ -188,14 +191,43 @@ export const FastApiCard = (props: FastApiCardProps) => { !tenant.aggregationApiKeysSecurity || !plan.aggregationApiKeysSecurity || filteredApiKeys.length <= 0 ) { - subscribe(apiId, team, plan); + openFormModal({ + title: translate("api.pricing.subscription.custom.name.help"), + schema: { + customName: { + type: type.string, + label: translate("Custom Name"), + } + }, + onSubmit: data => subscribe(apiId, team, plan, data.customName), + actionLabel: translate( + isSubscriptionProcessIsAutomatic(plan) + ? 'Get API key' + : 'Request API key' + ) + }) + } else { openApiKeySelectModal({ plan, apiKeys: filteredApiKeys, - onSubscribe: () => subscribe(apiId, team, plan), + onSubscribe: () => openFormModal({ + title: translate("api.pricing.subscription.custom.name.help"), + schema: { + customName: { + type: type.string, + label: translate("Custom Name"), + } + }, + onSubmit: data => subscribe(apiId, team, plan, data.customName), + actionLabel: translate( + isSubscriptionProcessIsAutomatic(plan) + ? 'Get API key' + : 'Request API key' + ) + }), extendApiKey: (apiKey: ISubscription) => - subscribe(apiId, team, plan, apiKey), + subscribe(apiId, team, plan, undefined, apiKey), }); } } @@ -301,24 +333,24 @@ export const FastApiCard = (props: FastApiCardProps) => { )} {((!subscriptionsCount && !isPending) || plan.allowMultipleKeys) && ( - - )} + + )} {isPending && (