From cc63d0c4e2a6fc3c84212e9942d993c63e04c916 Mon Sep 17 00:00:00 2001 From: Quentin Aubert Date: Mon, 16 Oct 2023 14:28:15 +0200 Subject: [PATCH 1/2] wip --- daikoku/app/controllers/ApiController.scala | 39 +++++++++---------- .../modals/ApiDocumentationSelectModal.tsx | 18 +++++---- .../javascript/src/contexts/navContext.tsx | 4 +- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/daikoku/app/controllers/ApiController.scala b/daikoku/app/controllers/ApiController.scala index 92280d0fc..2d636a81c 100644 --- a/daikoku/app/controllers/ApiController.scala +++ b/daikoku/app/controllers/ApiController.scala @@ -2007,7 +2007,7 @@ class ApiController( val pages = (ctx.request.body \ "pages").as[Seq[String]] (for { - fromPages <- env.dataStore.apiDocumentationPageRepo + documentationPages <- EitherT.liftF[Future, AppError, Seq[ApiDocumentationPage]](env.dataStore.apiDocumentationPageRepo .forTenant(ctx.tenant.id) .find( Json.obj( @@ -2015,8 +2015,8 @@ class ApiController( "$in" -> pages ) ) - ) - createdPages <- Future.sequence(fromPages.map(page => { + )) + createdPages <- EitherT.liftF[Future, AppError, Seq[ApiDocumentationDetailPage]](Future.sequence(documentationPages.map(page => { val generatedId = ApiDocumentationPageId(BSONObjectID.generate().stringify) env.dataStore.apiDocumentationPageRepo .forTenant(ctx.tenant.id) @@ -2024,24 +2024,21 @@ class ApiController( .flatMap(_ => FastFuture.successful( ApiDocumentationDetailPage(generatedId, page.title, Seq.empty) - )) - })) - api <- env.dataStore.apiRepo.findByVersion(ctx.tenant, - apiId, - version) - } yield { - api match { - case None => FastFuture.successful(AppError.render(ApiNotFound)) - case Some(api) => - env.dataStore.apiRepo - .forTenant(ctx.tenant.id) - .save( - api.copy(documentation = api.documentation - .copy(pages = api.documentation.pages ++ createdPages)) - ) - .map(_ => Ok(Json.obj("done" -> true))) - } - }).flatten + )) + }))) + api <- EitherT.fromOptionF[Future, AppError, Api](env.dataStore.apiRepo.findByVersion(ctx.tenant, + apiId, + version), AppError.ApiNotFound) + done <- EitherT.liftF[Future, AppError, Boolean](env.dataStore.apiRepo + .forTenant(ctx.tenant.id) + .save( + api.copy(documentation = api.documentation + .copy(pages = api.documentation.pages ++ createdPages)) + )) + + } yield Ok(Json.obj("done" -> done))) + .leftMap(_.render()) + .merge } } } diff --git a/daikoku/javascript/src/contexts/modals/ApiDocumentationSelectModal.tsx b/daikoku/javascript/src/contexts/modals/ApiDocumentationSelectModal.tsx index 9ac63b0fa..496ce9daa 100644 --- a/daikoku/javascript/src/contexts/modals/ApiDocumentationSelectModal.tsx +++ b/daikoku/javascript/src/contexts/modals/ApiDocumentationSelectModal.tsx @@ -51,14 +51,16 @@ export const ApiDocumentationSelectModal = (props: IApiDocumentationSelectModalP
{pagesQuery.isLoading && } {pagesQuery.data && !isError(pagesQuery.data) && ( - + )} {pagesQuery.data && isError(pagesQuery.data) && (
Error while fetching pages
diff --git a/daikoku/javascript/src/contexts/navContext.tsx b/daikoku/javascript/src/contexts/navContext.tsx index e71a85108..deb21147e 100644 --- a/daikoku/javascript/src/contexts/navContext.tsx +++ b/daikoku/javascript/src/contexts/navContext.tsx @@ -135,7 +135,7 @@ export const useApiFrontOffice = (api?: IApi, team?: ITeamSimple) => { }, className: { active: currentTab === 'documentation', - disabled: tenant.display === 'environment' && !api?.documentation?.pages?.length, + disabled: tenant.display === 'environment' || !api?.documentation?.pages?.length, 'd-none': tenant.display === 'environment' }, }, @@ -146,7 +146,7 @@ export const useApiFrontOffice = (api?: IApi, team?: ITeamSimple) => { }, className: { active: currentTab === 'swagger', - disabled: tenant.display === 'environment' && !api?.swagger?.content && !api?.swagger?.url, + disabled: tenant.display === 'environment' || !api?.swagger?.content && !api?.swagger?.url, 'd-none': tenant.display === 'environment' }, }, From 133aeb9ba10eb53fd7c1bd5161feb871df75e176 Mon Sep 17 00:00:00 2001 From: Quentin Aubert Date: Mon, 23 Oct 2023 17:05:38 +0200 Subject: [PATCH 2/2] FIX #588 --- daikoku/app/controllers/ApiController.scala | 75 +++++++++++++------ .../cypress/cypress-env-mode-export.ndjson | 43 +++++++++++ .../components/backoffice/apis/TeamApi.tsx | 28 +++---- .../backoffice/apis/TeamApiDocumentation.tsx | 37 +++++---- .../backoffice/apis/TeamApiPricings.tsx | 2 +- .../modals/ApiDocumentationSelectModal.tsx | 11 ++- .../javascript/src/contexts/modals/types.ts | 2 +- .../src/locales/en/translation.json | 1 + .../src/locales/fr/translation.json | 1 + daikoku/javascript/src/services/index.ts | 8 +- daikoku/javascript/src/types/api.ts | 1 + 11 files changed, 148 insertions(+), 61 deletions(-) create mode 100644 daikoku/javascript/cypress/cypress-env-mode-export.ndjson diff --git a/daikoku/app/controllers/ApiController.scala b/daikoku/app/controllers/ApiController.scala index 2d636a81c..8a883a0e8 100644 --- a/daikoku/app/controllers/ApiController.scala +++ b/daikoku/app/controllers/ApiController.scala @@ -512,10 +512,29 @@ class ApiController( s"@{user.name} has accessed documentation page for @{api.name} - @{api.id} - $pageId" ) )(ctx) { + + def verifyLink(api: Api, pageId: ApiDocumentationPageId): EitherT[Future, AppError, Boolean] = { + ctx.tenant.display match { + case TenantDisplay.Environment => + for{ + plans <- EitherT.liftF[Future, AppError, Seq[UsagePlan]](env.dataStore.usagePlanRepo.forTenant(ctx.tenant) + .find(Json.obj("_id" -> Json.obj("$in" -> JsArray(api.possibleUsagePlans.map(_.asJson)))))) + pages <- EitherT.pure[Future, AppError](plans.flatMap(_.documentation.map(_.pages).getOrElse(Seq.empty))) + } yield pages.exists(_.id == pageId) + case TenantDisplay.Default => + EitherT.liftF[Future, AppError, Boolean](env.dataStore.apiRepo.findAllVersions(ctx.tenant, api.id.value) + .map(apis => { + apis.filter(_.id != api.id).flatMap(api => api.documentation.docIds()) + }) + .map(_.exists(_ == pageId.value))) + } + } + + (for { api <- EitherT.fromOptionF[Future, AppError, Api](env.dataStore.apiRepo.forTenant(ctx.tenant.id).findByIdNotDeleted(apiId), AppError.ApiNotFound) page <- EitherT.fromOptionF[Future, AppError, ApiDocumentationPage](env.dataStore.apiDocumentationPageRepo.forTenant(ctx.tenant.id).findByIdNotDeleted(pageId), AppError.PageNotFound) -// _ <- EitherT.fromOption[Future](api.documentation.docIds().find(s => s == page.id.value), AppError.PageNotFound) + isLinked <- verifyLink(api, page.id) } yield { ctx.setCtxValue("api.id", api.id.value) ctx.setCtxValue("api.name", api.name) @@ -525,10 +544,10 @@ class ApiController( s"/api/apis/$apiId/pages/$pageId/content" ) Ok( - page.asWebUiJson.as[JsObject] ++ Json.obj("contentUrl" -> url) + page.asWebUiJson.as[JsObject] ++ Json.obj("contentUrl" -> url) ++ Json.obj("linked" -> isLinked) ) } else { - Ok(page.asWebUiJson) + Ok(page.asWebUiJson.as[JsObject] ++ Json.obj("linked" -> isLinked)) } }) .leftMap(_.render()) @@ -2005,6 +2024,7 @@ class ApiController( )(teamId, ctx) { _ => { val pages = (ctx.request.body \ "pages").as[Seq[String]] + val linked = (ctx.request.body \ "linked").asOpt[Boolean] (for { documentationPages <- EitherT.liftF[Future, AppError, Seq[ApiDocumentationPage]](env.dataStore.apiDocumentationPageRepo @@ -2016,16 +2036,20 @@ class ApiController( ) ) )) - createdPages <- EitherT.liftF[Future, AppError, Seq[ApiDocumentationDetailPage]](Future.sequence(documentationPages.map(page => { - val generatedId = ApiDocumentationPageId(BSONObjectID.generate().stringify) - env.dataStore.apiDocumentationPageRepo - .forTenant(ctx.tenant.id) - .save(page.copy(id = generatedId)) - .flatMap(_ => - FastFuture.successful( - ApiDocumentationDetailPage(generatedId, page.title, Seq.empty) - )) - }))) + createdPages <- EitherT.liftF[Future, AppError, Seq[ApiDocumentationDetailPage]]( + linked match { + case Some(true) => FastFuture.successful(documentationPages.map(page => ApiDocumentationDetailPage(page.id, page.title, Seq.empty))) + case _ => Future.sequence(documentationPages.map(page => { + val generatedId = ApiDocumentationPageId(BSONObjectID.generate().stringify) + env.dataStore.apiDocumentationPageRepo + .forTenant(ctx.tenant.id) + .save(page.copy(id = generatedId)) + .flatMap(_ => + FastFuture.successful( + ApiDocumentationDetailPage(generatedId, page.title, Seq.empty) + )) + })) + }) api <- EitherT.fromOptionF[Future, AppError, Api](env.dataStore.apiRepo.findByVersion(ctx.tenant, apiId, version), AppError.ApiNotFound) @@ -2051,6 +2075,7 @@ class ApiController( )(teamId, ctx) { _ => { val pages = (ctx.request.body \ "pages").as[Seq[String]] + val linked = (ctx.request.body \ "linked").asOpt[Boolean] (for { fromPages <- EitherT.liftF[Future, AppError, Seq[ApiDocumentationPage]](env.dataStore.apiDocumentationPageRepo @@ -2062,16 +2087,20 @@ class ApiController( ) ) )) - createdPages <- EitherT.liftF[Future, AppError, Seq[ApiDocumentationDetailPage]](Future.sequence(fromPages.map(page => { - val generatedId = ApiDocumentationPageId(BSONObjectID.generate().stringify) - env.dataStore.apiDocumentationPageRepo - .forTenant(ctx.tenant.id) - .save(page.copy(id = generatedId)) - .flatMap(_ => - FastFuture.successful( - ApiDocumentationDetailPage(generatedId, page.title, Seq.empty) - )) - }))) + createdPages <- EitherT.liftF[Future, AppError, Seq[ApiDocumentationDetailPage]]( + linked match { + case Some(true) => FastFuture.successful(fromPages.map(page => ApiDocumentationDetailPage(page.id, page.title, Seq.empty))) + case _ => Future.sequence(fromPages.map(page => { + val generatedId = ApiDocumentationPageId(BSONObjectID.generate().stringify) + env.dataStore.apiDocumentationPageRepo + .forTenant(ctx.tenant.id) + .save(page.copy(id = generatedId)) + .flatMap(_ => + FastFuture.successful( + ApiDocumentationDetailPage(generatedId, page.title, Seq.empty) + )) + })) + }) plan <- EitherT.fromOptionF(env.dataStore.usagePlanRepo.forTenant(ctx.tenant).findByIdNotDeleted(planId), AppError.PlanNotFound) _ <- EitherT.liftF[Future, AppError, Boolean](env.dataStore.usagePlanRepo .forTenant(ctx.tenant.id) diff --git a/daikoku/javascript/cypress/cypress-env-mode-export.ndjson b/daikoku/javascript/cypress/cypress-env-mode-export.ndjson new file mode 100644 index 000000000..e01b0dc74 --- /dev/null +++ b/daikoku/javascript/cypress/cypress-env-mode-export.ndjson @@ -0,0 +1,43 @@ +{"type":"tenants","payload":{"_id":"default","name":"Evil Corp.","style":{"js":"","css":"","logo":"/assets/images/daikoku.svg","jsUrl":null,"title":"Evil Corp.","cssUrl":null,"footer":null,"cacheTTL":60000,"colorTheme":":root {\n --error-color: #ff6347;\n --error-color: #ffa494;\n --success-color: #4F8A10;\n --success-color: #76cf18;\n\n --link-color: #7f96af;\n --link--hover-color: #8fa6bf;\n\n --body-bg-color: #fff;\n --body-text-color: #212529;\n --navbar-bg-color: #7f96af;\n --navbar-brand-color: #fff;\n --menu-bg-color: #fff;\n --menu-text-color: #212529;\n --menu-text-hover-bg-color: #9bb0c5;\n --menu-text-hover-color: #fff;\n --section-bg-color: #f8f9fa;\n --section-text-color: #6c757d;\n --section-bottom-color: #eee;\n --addContent-bg-color: #e9ecef;\n --addContent-text-color: #000;\n --sidebar-bg-color: #f8f9fa;\n\n --btn-bg-color: #fff;\n --btn-text-color: #495057;\n --btn-border-color: #97b0c7;\n\n --badge-tags-bg-color: #ffc107;\n --badge-tags-bg-color: #ffe1a7;\n --badge-tags-text-color: #212529;\n\n --pagination-text-color: #586069;\n --pagination-border-color: #586069;\n\n --table-bg-color: #f8f9fa;\n\n --apicard-visibility-color: #586069;\n --apicard-visibility-border-color: rgba(27,31,35,.15);\n --modal-selection-bg-color: rgba(27,31,35,.15);\n}","faviconUrl":null,"description":"A new organization to host very fine APIs","homeCmsPage":"63c67b5150010024104b0e8c","unloggedHome":"","fontFamilyUrl":null,"homePageVisible":false,"notFoundCmsPage":null,"cmsHistoryLength":10,"authenticatedCmsPage":null},"domain":"localhost","contact":"contact@foo.bar","display":"environment","enabled":true,"_deleted":false,"adminApi":"admin-api-tenant-default","robotTxt":null,"isPrivate":false,"tenantMode":"Default","authProvider":"Local","environments":["dev","prod"],"bucketSettings":null,"defaultMessage":null,"mailerSettings":{"type":"console","template":null},"defaultLanguage":"En","_humanReadableId":"evil-corp.","auditTrailConfig":{"kafkaConfig":null,"alertsEmails":[],"auditWebhooks":[],"elasticConfigs":null},"creationSecurity":false,"otoroshiSettings":[{"_id":"default","url":"http://127.0.0.1:9000/fakeotoroshi","host":"otoroshi-api.foo.bar","clientId":"admin-api-apikey-id","clientSecret":"admin-api-apikey-id"}],"adminSubscriptions":[],"authProviderSettings":{"sessionMaxAge":86400},"subscriptionSecurity":true,"apiReferenceHideForGuest":true,"thirdPartyPaymentSettings":[],"aggregationApiKeysSecurity":false}} +{"type":"users","payload":{"_id":"7cxa2bvog4k8e8d3uswmwnf90w9x8nea","name":"User","email":"user@foo.bar","isGuest":false,"origins":["Local"],"picture":"/assets/images/anonymous.jpg","tenants":["default"],"_deleted":false,"metadata":{},"password":"$2a$10$/Vpj1lFN0AcCfbutd7FJwO0j1vU4X0fR6t.4vvWBqSEqtoUFFxfDG","invitation":null,"lastTenant":null,"starredApis":[],"personalToken":"m775o13fduvnmpygimyyd6jpgh6ephl7","isDaikokuAdmin":false,"defaultLanguage":null,"_humanReadableId":"user-foo.bar","pictureFromProvider":true,"twoFactorAuthentication":null,"hardwareKeyRegistrations":[]}} +{"type":"users","payload":{"_id":"mom9ff7opam5nv576rpo98n9sqpxag5l","name":"Tester","email":"tester@foo.bar","isGuest":false,"origins":["Local"],"picture":"https://www.gravatar.com/avatar/7075180bc9c01c6ece2925b9822b6d77?size=128&d=robohash","tenants":["default"],"_deleted":false,"metadata":{},"password":"$2a$10$/Vpj1lFN0AcCfbutd7FJwO0j1vU4X0fR6t.4vvWBqSEqtoUFFxfDG","invitation":null,"lastTenant":null,"starredApis":[],"personalToken":"z05xtwwlgf1vlj4q1xkr6j0kbdjrz8q0","isDaikokuAdmin":false,"defaultLanguage":null,"_humanReadableId":"tester-foo.bar","pictureFromProvider":true,"twoFactorAuthentication":null,"hardwareKeyRegistrations":[]}} +{"type":"users","payload":{"_id":"SR9OKNdEcz1CZwe8WEMB6QYvFQPGfDTs6RXHDGYMCIva0SjEA11EOpmbMzYvBrQL","name":"Admin","email":"admin@foo.bar","isGuest":false,"origins":["Local"],"picture":"https://www.gravatar.com/avatar/87d47b53c88259042a50fa24a6d6b0f4?size=128&d=robohash","tenants":["default"],"_deleted":false,"metadata":{},"password":"$2a$10$/Vpj1lFN0AcCfbutd7FJwO0j1vU4X0fR6t.4vvWBqSEqtoUFFxfDG","invitation":null,"lastTenant":null,"starredApis":[],"personalToken":"bvBvXclSTmCmdAmEN2W03zTDwga7nKtZ","isDaikokuAdmin":true,"defaultLanguage":null,"_humanReadableId":"admin-foo.bar","pictureFromProvider":true,"twoFactorAuthentication":null,"hardwareKeyRegistrations":[]}} +{"type":"user_sessions","payload":{"_id":"65368217590100e9053d8279","ttl":86400000,"userId":"SR9OKNdEcz1CZwe8WEMB6QYvFQPGfDTs6RXHDGYMCIva0SjEA11EOpmbMzYvBrQL","created":1698071063273,"expires":1698157463273,"userName":"Admin","sessionId":"Y81TJhH2ix0oYJj2l98NgqqxwByAtd8HMNyW53l3Ow2nbMUkcdLb78BePrUcA1X8","userEmail":"admin@foo.bar","impersonatorId":null,"impersonatorName":null,"impersonatorEmail":null,"impersonatorSessionId":null}} +{"type":"evolutions","payload":{"_id":"63c67b5050010024104b0e8a","date":1673952080925,"applied":true,"version":"1.0.2"}} +{"type":"evolutions","payload":{"_id":"63c67b5050010024104b0e8b","date":1673952080998,"applied":true,"version":"1.5.0"}} +{"type":"evolutions","payload":{"_id":"63c67b5150010024104b0e8d","date":1673952081023,"applied":true,"version":"1.5.1"}} +{"type":"evolutions","payload":{"_id":"63c67b5150010024104b0e8e","date":1673952081073,"applied":true,"version":"1.5.5"}} +{"type":"evolutions","payload":{"_id":"63c67b5150010024104b0e8f","date":1673952081523,"applied":true,"version":"1.5.7"}} +{"type":"evolutions","payload":{"_id":"63c67b5150010024104b0e8g","date":1673952081553,"applied":true,"version":"1.5.7_b"}} +{"type":"evolutions","payload":{"_id":"63fdd9685f010094d3f142e9","date":1677580648360,"applied":true,"version":"1.5.7_c"}} +{"type":"evolutions","payload":{"_id":"63fdd9685f010094d3f142ea","date":1677580648557,"applied":true,"version":"16.1.2"}} +{"type":"evolutions","payload":{"_id":"64b50d34510100d33557b773","date":1689586996529,"applied":true,"version":"16.1.2_a"}} +{"type":"evolutions","payload":{"_id":"64b50d34510100d33557b774","date":1689586996540,"applied":true,"version":"16.1.2_b"}} +{"type":"evolutions","payload":{"_id":"64b50d34510100d33557b775","date":1689586996568,"applied":true,"version":"16.1.2_c"}} +{"type":"evolutions","payload":{"_id":"64b50d34510100d33557b776","date":1689586996606,"applied":true,"version":"16.1.3"}} +{"type":"evolutions","payload":{"_id":"64b50d34510100d33557b777","date":1689586996608,"applied":true,"version":"16.1.3_b"}} +{"type":"evolutions","payload":{"_id":"64b50d34510100d33557b778","date":1689586996610,"applied":true,"version":"16.3.0"}} +{"type":"teams","payload":{"_id":"5ffd5cd6260100461a3cc701","name":"Admin","type":"Personal","users":[{"userId":"SR9OKNdEcz1CZwe8WEMB6QYvFQPGfDTs6RXHDGYMCIva0SjEA11EOpmbMzYvBrQL","teamPermission":"Administrator"}],"avatar":"https://www.gravatar.com/avatar/87d47b53c88259042a50fa24a6d6b0f4?size=128&d=robohash","_tenant":"default","contact":"admin@foo.bar","_deleted":false,"metadata":{},"verified":true,"description":"The personal team of Admin","_humanReadableId":"admin","apiKeyVisibility":null,"apisCreationPermission":null,"authorizedOtoroshiGroups":[]}} +{"type":"teams","payload":{"_id":"5ffd5d30260100461a3cc730","name":"Testers","type":"Organization","users":[{"userId":"SR9OKNdEcz1CZwe8WEMB6QYvFQPGfDTs6RXHDGYMCIva0SjEA11EOpmbMzYvBrQL","teamPermission":"Administrator"},{"userId":"mom9ff7opam5nv576rpo98n9sqpxag5l","teamPermission":"Administrator"}],"avatar":"/assets/images/daikoku.svg","_tenant":"default","contact":"contact@foo.bar","_deleted":false,"metadata":{},"verified":true,"description":"A new team","_humanReadableId":"testers","apiKeyVisibility":"User","apisCreationPermission":null,"authorizedOtoroshiGroups":[]}} +{"type":"teams","payload":{"_id":"5ffd5d9c260100461a3cc74f","name":"Tester","type":"Personal","users":[{"userId":"mom9ff7opam5nv576rpo98n9sqpxag5l","teamPermission":"Administrator"}],"avatar":"https://www.gravatar.com/avatar/7075180bc9c01c6ece2925b9822b6d77?size=128&d=robohash","_tenant":"default","contact":"tester@foo.bar","_deleted":false,"metadata":{},"verified":true,"description":"The personal team of Tester","_humanReadableId":"tester","apiKeyVisibility":null,"apisCreationPermission":null,"authorizedOtoroshiGroups":[]}} +{"type":"teams","payload":{"_id":"5ffd5f7e260100461a3cc845","name":"Consumers","type":"Organization","users":[{"userId":"mom9ff7opam5nv576rpo98n9sqpxag5l","teamPermission":"Administrator"}],"avatar":"https://www.gravatar.com/avatar/4456b5af662a51b8d3895093bb5dbeff?size=128&d=robohash","_tenant":"default","contact":"consumers@foo.bar","_deleted":false,"metadata":{},"verified":true,"description":"A new team to consume APIs","_humanReadableId":"consumers","apiKeyVisibility":"User","apisCreationPermission":null,"authorizedOtoroshiGroups":[]}} +{"type":"teams","payload":{"_id":"600fdfb32c0100f3 3b3412ff","name":"User","type":"Personal","users":[{"userId":"7cxa2bvog4k8e8d3uswmwnf90w9x8nea","teamPermission":"Administrator"}],"avatar":"/assets/images/anonymous.jpg","_tenant":"default","contact":"user@foo.bar","_deleted":false,"metadata":{},"verified":true,"description":"The personal team of User","_humanReadableId":"user","apiKeyVisibility":null,"apisCreationPermission":null,"authorizedOtoroshiGroups":[]}} +{"type":"teams","payload":{"_id":"0k7D3RIkcDwsZJQ36ml6A6qjC1PdeiY4U0pBRQDX3uyBsdkYJYdDaBc0E1YnKQFC","name":"evil-corp.-admin-team","type":"Admin","users":[{"userId":"rFCx7t82uKpXBbNgTL0J345PYxC1VadCs227JApm6M0E4DDGCSnpzOXIQ2yDG8lx","teamPermission":"Administrator"},{"userId":"SR9OKNdEcz1CZwe8WEMB6QYvFQPGfDTs6RXHDGYMCIva0SjEA11EOpmbMzYvBrQL","teamPermission":"Administrator"},{"userId":"T4ViSTwpsdTJZrCIaKeYios2zUrgVDZcvQI4FoqtQMYPFrdfIch88ccepuCpJfeF","teamPermission":"Administrator"},{"userId":"kJoE8yRSbUbEecueRpDDNn5V2Fk3GNuMMSFeDFc9eGGgnwLZYJOfe4Z4YvffdrHB","teamPermission":"Administrator"}],"avatar":"/assets/images/daikoku.svg","_tenant":"default","contact":"contact@foo.bar","_deleted":false,"metadata":{},"verified":true,"description":"The admin team for the default tenant","_humanReadableId":"evil-corp.-admin-team","apiKeyVisibility":null,"apisCreationPermission":null,"authorizedOtoroshiGroups":[]}} +{"type":"apis","payload":{"_id":"5ffd5e4d260100461a3ccqsds","apis":null,"name":"public with permissions API","tags":["test"],"team":"5ffd5d30260100461a3cc730","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":{"url":"/assets/swaggers/petstore.json","content":null,"headers":{}},"testing":{"auth":"Basic","name":"test auth","config":null,"enabled":true,"password":"client-secret","username":"client-id"},"_deleted":false,"isDefault":true,"categories":["internal"],"issuesTags":[],"lastUpdate":1610440269091,"visibility":"PublicWithAuthorizations","description":"# Title\n\n## subtitle\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida convallis leo et aliquet. Aenean venenatis, elit et dignissim scelerisque, urna dui mollis nunc, id eleifend velit sem et ante. Quisque pharetra sed tellus id finibus. In quis porta libero. Nunc egestas eros elementum lacinia blandit. Donec nisi lacus, tristique vel blandit in, sodales eget lacus. Phasellus ultrices magna vel odio vestibulum, a rhoncus nunc ornare. Sed laoreet finibus arcu vitae aliquam. Aliquam quis ex dui.","documentation":{"_id":"5ffd5e4d260100461a3cc7b7","pages":[],"_tenant":"default","lastModificationAt":1610440269091},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"public-with-permissions-api","defaultUsagePlan":"default","smallDescription":"A test API for test public with authorizations access","supportedVersions":["1.0.0"],"possibleUsagePlans":[]}} +{"type":"apis","payload":{"_id":"admin-api-tenant-default","apis":null,"name":"admin-api-tenant-default","tags":["Administration"],"team":"0k7D3RIkcDwsZJQ36ml6A6qjC1PdeiY4U0pBRQDX3uyBsdkYJYdDaBc0E1YnKQFC","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":{"url":null,"content":null,"headers":{}},"testing":{"auth":"Basic","name":null,"config":null,"enabled":false,"password":null,"username":null},"_deleted":false,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1610439860223,"visibility":"AdminOnly","description":"admin api","documentation":{"_id":"5ffd5cb4260100461a3cc6b6","pages":[],"_tenant":"default","lastModificationAt":1610439860223},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"admin-api-tenant-default","defaultUsagePlan":"1","smallDescription":"admin api","supportedVersions":["1.0.0"],"possibleUsagePlans":["admin"]}} +{"type":"apis","payload":{"_id":"5ffd5e4d260100461a3cc7b6","apis":null,"name":"test API","tags":["test"],"team":"5ffd5d30260100461a3cc730","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":{"url":"/assets/swaggers/petstore.json","content":null,"headers":{}},"testing":{"auth":"Basic","name":"test auth","config":null,"enabled":true,"password":"client-secret","username":"client-id"},"_deleted":false,"isDefault":false,"categories":["internal"],"issuesTags":[],"lastUpdate":1610440269091,"visibility":"Public","description":"# Title\n\n## subtitle\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida convallis leo et aliquet. Aenean venenatis, elit et dignissim scelerisque, urna dui mollis nunc, id eleifend velit sem et ante. Quisque pharetra sed tellus id finibus. In quis porta libero. Nunc egestas eros elementum lacinia blandit. Donec nisi lacus, tristique vel blandit in, sodales eget lacus. Phasellus ultrices magna vel odio vestibulum, a rhoncus nunc ornare. Sed laoreet finibus arcu vitae aliquam. Aliquam quis ex dui.","documentation":{"_id":"5ffd5e4d260100461a3cc7b7","pages":[{"id":"fjbxdprhtd12f3u91gejepx21vkppatg","title":"Introduction","children":[]}],"_tenant":"default","lastModificationAt":1610440269091},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"test-api","defaultUsagePlan":"default","smallDescription":"A test API for test","supportedVersions":["1.0.0"],"possibleUsagePlans":["lhsc79x9s0p4drv8j3ebapwrbnqhu1nh","lhsc79x9s0p4drv8j3ebapwrbnqhu1nq"]}} +{"type":"apis","payload":{"_id":"5ffd5e4d260100461a3cc7b8","apis":null,"name":"test API","tags":["test"],"team":"5ffd5d30260100461a3cc730","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":"5ffd5e4d260100461a3cc7b6","_tenant":"default","swagger":{"url":"/assets/swaggers/petstore.json","content":null,"headers":{}},"testing":{"auth":"Basic","name":"test auth","config":null,"enabled":true,"password":"client-secret","username":"client-id"},"_deleted":false,"isDefault":true,"categories":["internal"],"issuesTags":[],"lastUpdate":1610440269091,"visibility":"Public","description":"# Title\n\n## subtitle\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida convallis leo et aliquet. Aenean venenatis, elit et dignissim scelerisque, urna dui mollis nunc, id eleifend velit sem et ante. Quisque pharetra sed tellus id finibus. In quis porta libero. Nunc egestas eros elementum lacinia blandit. Donec nisi lacus, tristique vel blandit in, sodales eget lacus. Phasellus ultrices magna vel odio vestibulum, a rhoncus nunc ornare. Sed laoreet finibus arcu vitae aliquam. Aliquam quis ex dui.","documentation":{"_id":"5ffd5e4d260100461a3cc7b7","pages":[],"_tenant":"default","lastModificationAt":1610440269091},"currentVersion":"2.0.0","authorizedTeams":[],"_humanReadableId":"test-api","defaultUsagePlan":"default","smallDescription":"A test API for test","supportedVersions":["1.0.0"],"possibleUsagePlans":["mom9ff7opam5nv576rpo98n9sqpxagoo","lhsc79x9s0p4drv8j3ebapwrbnqhu1oo"]}} +{"type":"api_subscriptions","payload":{"by":"SR9OKNdEcz1CZwe8WEMB6QYvFQPGfDTs6RXHDGYMCIva0SjEA11EOpmbMzYvBrQL","_id":"5ffd5faf260100461a3cc870","api":"5ffd5e4d260100461a3cc7b6","plan":"lhsc79x9s0p4drv8j3ebapwrbnqhu1nh","tags":[],"team":"5ffd5f7e260100461a3cc845","apiKey":{"clientId":"KN25jHI9jDMCP0xvhmvIM4dKDK7LxuSw","clientName":"daikoku-api-key-test-api-not-test-plan-consumers-1610440623012","clientSecret":"Fn5tUh6CgLgSaEJYX7QRZD2XeVPNm06s9aqJFiXHfErpBDyEQr1ezdH47MZ9iRVM"},"parent":null,"_tenant":"default","enabled":true,"_deleted":false,"metadata":{},"rotation":{"enabled":false,"gracePeriod":168,"rotationEvery":744,"pendingRotation":false},"createdAt":1610440623012,"customName":null,"customMetadata":null,"customReadOnly":null,"adminCustomName":null,"customMaxPerDay":null,"integrationToken":"FbpezJzgj2DnD7s3LUsSI4GPig6gtGHKuk5aSMjp7KGQxpLn0tUahkWCsUR0c9Gy","customMaxPerMonth":null,"customMaxPerSecond":null,"thirdPartySubscriptionInformations":null}} +{"type":"api_subscriptions","payload":{"by":"mom9ff7opam5nv576rpo98n9sqpxag5l","_id":"5ffda7182f010014e747f8ff","api":"5ffd5e4d260100461a3cc7b6","plan":"lhsc79x9s0p4drv8j3ebapwrbnqhu1nh","tags":[],"team":"5ffd5d30260100461a3cc730","apiKey":{"clientId":"8YDhS4iph27bvPMgqGw6MyRnf4PcaT55","clientName":"daikoku-api-key-test-api-not-test-plan-testers-1610458904204","clientSecret":"yiWMRWxA3jXP7Rgr6UwwRsdEYC0bjg5ucXDKBiy5iazpdclOmKfpM47NejntHxbS"},"parent":null,"_tenant":"default","enabled":true,"_deleted":false,"metadata":{},"rotation":{"enabled":false,"gracePeriod":168,"rotationEvery":744,"pendingRotation":false},"createdAt":1610458904204,"customName":null,"customMetadata":null,"customReadOnly":null,"adminCustomName":null,"customMaxPerDay":null,"integrationToken":"JsSrH8UUvKkeMpI9yN6kRJdQwWDJTAsf6LeoS8lrLY0kCPsIIolojOHFEpjIYIPb","customMaxPerMonth":null,"customMaxPerSecond":null,"thirdPartySubscriptionInformations":null}} +{"type":"api_documentation_pages","payload":{"_id":"fjbxdprhtd12f3u91gejepx21vkppatg","title":"Introduction","_tenant":"default","content":"# Introduction\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida convallis leo et aliquet. Aenean venenatis, elit et dignissim scelerisque, urna dui mollis nunc, id eleifend velit sem et ante. Quisque pharetra sed tellus id finibus. In quis porta libero. Nunc egestas eros elementum lacinia blandit. Donec nisi lacus, tristique vel blandit in, sodales eget lacus. Phasellus ultrices magna vel odio vestibulum, a rhoncus nunc ornare. Sed laoreet finibus arcu vitae aliquam. Aliquam quis ex dui.\n\nCras ut ultrices quam. Nulla eu purus sed turpis consequat sodales. Aenean vitae efficitur velit, vel accumsan felis. Curabitur aliquam odio dictum urna convallis faucibus. Vivamus eu dignissim lorem. Donec sed hendrerit massa. Suspendisse volutpat, nisi at fringilla consequat, eros lacus aliquam metus, eu convallis nulla mauris quis lacus. Aliquam ultricies, mi eget feugiat vestibulum, enim nunc eleifend nisi, nec tincidunt turpis elit id diam. Nunc placerat accumsan tincidunt. Nulla ut interdum dui. Praesent venenatis cursus aliquet. Nunc pretium rutrum felis nec pharetra.\n\nVivamus sapien ligula, hendrerit a libero vitae, convallis maximus massa. Praesent ante leo, fermentum vitae libero finibus, blandit porttitor risus. Nulla ac hendrerit turpis. Sed varius velit at libero feugiat luctus. Nunc rhoncus sem dolor, nec euismod justo rhoncus vitae. Vivamus finibus nulla a purus vestibulum sagittis. Maecenas maximus orci at est lobortis, nec facilisis erat rhoncus. Sed tempus leo et est dictum lobortis. Vestibulum rhoncus, nisl ut porta sollicitudin, arcu urna egestas arcu, eget efficitur neque ipsum ut felis. Ut commodo purus quis turpis tempus tincidunt. Donec id hendrerit eros. Vestibulum vitae justo consectetur, egestas nisi ac, eleifend odio.\n\nDonec id mi cursus, volutpat dolor sed, bibendum sapien. Etiam vitae mauris sit amet urna semper tempus vel non metus. Integer sed ligula diam. Aenean molestie ultrices libero eget suscipit. Phasellus maximus euismod eros ut scelerisque. Ut quis tempus metus. Sed mollis volutpat velit eget pellentesque. Integer hendrerit ultricies massa eu tincidunt. Quisque at cursus augue. Sed diam odio, molestie sed dictum eget, efficitur nec nulla. Nullam vulputate posuere nunc nec laoreet. Integer varius sed erat vitae cursus. Vivamus auctor augue enim, a fringilla mauris molestie eget.\n\nProin vehicula ligula vel enim euismod, sed congue mi egestas. Nullam varius ut felis eu fringilla. Quisque sodales tortor nec justo tristique, sit amet consequat mi tincidunt. Suspendisse porttitor laoreet velit, non gravida nibh cursus at. Pellentesque faucibus, tellus in dapibus viverra, dolor mi dignissim tortor, id convallis ipsum lorem id nisl. Sed id nisi felis. Aliquam in ullamcorper ipsum, vel consequat magna. Donec nec mollis lacus, a euismod elit.","_deleted":false,"contentType":"text/markdown","_humanReadableId":"fjbxdprhtd12f3u91gejepx21vkppatg","remoteContentUrl":null,"lastModificationAt":1610440383046,"remoteContentEnabled":false,"remoteContentHeaders":{}}} +{"type":"api_documentation_pages","payload":{"_id":"pEHAF3XIAcnouFpNH3KprqXK0N3Rh9tP","title":"Introduction","_tenant":"default","content":"# Introduction\n\nA new page","_deleted":false,"contentType":"text/markdown","_humanReadableId":"pEHAF3XIAcnouFpNH3KprqXK0N3Rh9tP","remoteContentUrl":null,"lastModificationAt":1698071182565,"remoteContentEnabled":false,"remoteContentHeaders":{}}} +{"type":"notifications","payload":{"_id":"5ffd5db5260100461a3cc75b","date":1610440117890,"team":null,"action":{"team":"5ffd5d30260100461a3cc730","type":"TeamInvitation","user":"mom9ff7opam5nv576rpo98n9sqpxag5l"},"sender":{"id":null,"name":"Admin","email":"admin@foo.bar"},"status":{"date":1610440134095,"status":"Accepted"},"_tenant":"default","_deleted":false,"notificationType":"AcceptOrReject"}} +{"type":"notifications","payload":{"_id":"64afc0a14401004c626d7e7a","date":1689239713306,"team":"5ffd5d30260100461a3cc730","action":{"api":"5ffd5e4d260100461a3cc7b6","plan":"lhsc79x9s0p4drv8j3ebapwrbnqhu1nq","step":"COENV5oHzD9CBGW4K3JfFRKVrH2ReZBSqX67dzVGWwWwDKragGwYHYOAQIVsF6Za","team":"5ffd5f7e260100461a3cc845","type":"ApiSubscription","demand":"64afc0a14401004c626d7e79","motivation":"motivation","parentSubscriptionId":null},"sender":{"id":"SR9OKNdEcz1CZwe8WEMB6QYvFQPGfDTs6RXHDGYMCIva0SjEA11EOpmbMzYvBrQL","name":"Admin","email":"admin@foo.bar"},"status":{"status":"Pending"},"_tenant":"default","_deleted":false,"notificationType":"AcceptOrReject"}} +{"type":"consumptions","payload":{"to":1610440643277,"_id":"5ffd5fc3260100461a3cc898","api":"5ffd5e4d260100461a3cc7b6","from":1610406000000,"hits":17,"plan":"lhsc79x9s0p4drv8j3ebapwrbnqhu1nh","team":"5ffd5f7e260100461a3cc845","state":"completed","quotas":{"currentCallsPerDay":936,"currentCallsPerSec":1,"currentCallsPerMonth":528,"remainingCallsPerDay":64,"remainingCallsPerSec":9,"authorizedCallsPerDay":1000,"authorizedCallsPerSec":10,"remainingCallsPerMonth":472,"authorizedCallsPerMonth":1000},"_tenant":"default","billing":{"hits":17,"total":0},"_deleted":false,"clientId":"KN25jHI9jDMCP0xvhmvIM4dKDK7LxuSw","globalInformations":{"hits":17,"dataIn":0,"dataOut":0,"avgDuration":null,"avgOverhead":null}}} +{"type":"consumptions","payload":{"to":1610460438126,"_id":"5ffdad16340100b496030b23","api":"5ffd5e4d260100461a3cc7b6","from":1610406000000,"hits":33,"plan":"lhsc79x9s0p4drv8j3ebapwrbnqhu1nh","team":"5ffd5d30260100461a3cc730","state":"completed","quotas":{"currentCallsPerDay":234,"currentCallsPerSec":5,"currentCallsPerMonth":600,"remainingCallsPerDay":766,"remainingCallsPerSec":5,"authorizedCallsPerDay":1000,"authorizedCallsPerSec":10,"remainingCallsPerMonth":400,"authorizedCallsPerMonth":1000},"_tenant":"default","billing":{"hits":33,"total":0},"_deleted":false,"clientId":"8YDhS4iph27bvPMgqGw6MyRnf4PcaT55","globalInformations":{"hits":33,"dataIn":0,"dataOut":0,"avgDuration":null,"avgOverhead":null}}} +{"type":"subscription_demands","payload":{"_id":"64afc0a14401004c626d7e79","api":"5ffd5e4d260100461a3cc7b6","date":1689239713283,"from":"SR9OKNdEcz1CZwe8WEMB6QYvFQPGfDTs6RXHDGYMCIva0SjEA11EOpmbMzYvBrQL","plan":"lhsc79x9s0p4drv8j3ebapwrbnqhu1nq","team":"5ffd5f7e260100461a3cc845","state":"inProgress","steps":[{"_id":"COENV5oHzD9CBGW4K3JfFRKVrH2ReZBSqX67dzVGWwWwDKragGwYHYOAQIVsF6Za","step":{"id":"123","team":"5ffd5d30260100461a3cc730","type":"teamAdmin","title":"team admin","schema":{"motivation":{"type":"string","format":"textarea","constraints":[{"type":"required"}]}},"formatter":"[[motivation]]"},"state":"inProgress","metadata":{}}],"_tenant":"default","_deleted":false,"motivation":{"motivation":"motivation"},"customMetadata":null,"customReadOnly":null,"adminCustomName":null,"customMaxPerDay":null,"customMaxPerMonth":null,"customMaxPerSecond":null,"parentSubscription":null}} +{"type":"usage_plans","payload":{"_id":"admin","type":"Admin","_tenant":"default","_deleted":false,"customName":"Administration plan","otoroshiTarget":null,"allowMultipleKeys":true,"customDescription":"access to admin api","subscriptionProcess":[],"aggregationApiKeysSecurity":false}} +{"type":"usage_plans","payload":{"_id":"lhsc79x9s0p4drv8j3ebapwrbnqhu1oo","type":"FreeWithQuotas","_tenant":"default","swagger":{"url":null,"content":null,"headers":{}},"testing":{"auth":"Basic","name":null,"config":null,"enabled":false,"password":null,"username":null},"_deleted":false,"currency":{"code":"EUR"},"maxPerDay":1000,"customName":"dev","visibility":"Public","maxPerMonth":1000,"autoRotation":false,"maxPerSecond":10,"documentation":null,"otoroshiTarget":{"otoroshiSettings":"default","authorizedEntities":{"groups":[],"routes":[],"services":[]},"apikeyCustomization":{"tags":[],"metadata":{},"readOnly":false,"clientIdOnly":false,"restrictions":{"allowed":[],"enabled":false,"notFound":[],"allowLast":true,"forbidden":[]},"customMetadata":[],"constrainedServicesOnly":false}},"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":false,"customDescription":"fake prod plan (^_-)","integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":null}} +{"type":"usage_plans","payload":{"_id":"mom9ff7opam5nv576rpo98n9sqpxagoo","type":"FreeWithQuotas","_tenant":"default","swagger":{"url":null,"content":null,"headers":{}},"testing":{"auth":"Basic","name":null,"config":null,"enabled":false,"password":null,"username":null},"_deleted":false,"currency":{"code":"EUR"},"maxPerDay":500,"customName":"prod","visibility":"Public","maxPerMonth":10000,"autoRotation":false,"maxPerSecond":10,"documentation":null,"otoroshiTarget":{"otoroshiSettings":"default","authorizedEntities":{"groups":[],"routes":[],"services":[]},"apikeyCustomization":{"tags":[],"metadata":{},"readOnly":false,"clientIdOnly":false,"restrictions":{"allowed":[],"enabled":false,"notFound":[],"allowLast":true,"forbidden":[]},"customMetadata":[],"constrainedServicesOnly":false}},"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":false,"customDescription":"Free plan with limited number of calls per day and per month","integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":null}} +{"type":"usage_plans","payload":{"_id":"lhsc79x9s0p4drv8j3ebapwrbnqhu1nq","type":"FreeWithQuotas","_tenant":"default","swagger":{"url":null,"content":null,"headers":{}},"testing":{"auth":"Basic","name":null,"config":null,"enabled":false,"password":null,"username":null},"_deleted":false,"currency":{"code":"EUR"},"maxPerDay":500,"customName":"prod","visibility":"Public","maxPerMonth":10000,"autoRotation":false,"maxPerSecond":10,"documentation":null,"otoroshiTarget":{"otoroshiSettings":"default","authorizedEntities":{"groups":[],"routes":["s_123456"],"services":[]},"apikeyCustomization":{"tags":[],"metadata":{},"readOnly":false,"clientIdOnly":false,"restrictions":{"allowed":[],"enabled":false,"notFound":[],"allowLast":true,"forbidden":[]},"customMetadata":[],"constrainedServicesOnly":false}},"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":false,"customDescription":"Free plan with limited number of calls per day and per month","integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":null}} +{"type":"usage_plans","payload":{"_id":"lhsc79x9s0p4drv8j3ebapwrbnqhu1nh","type":"FreeWithQuotas","_tenant":"default","swagger":{"url":null,"content":null,"headers":{}},"testing":{"auth":"Basic","name":null,"config":null,"enabled":false,"password":null,"username":null},"_deleted":false,"currency":{"code":"EUR"},"maxPerDay":1000,"customName":"dev","visibility":"Public","maxPerMonth":1000,"autoRotation":false,"maxPerSecond":10,"documentation":{"_id":"6536828d590100e9053d8382","pages":[{"id":"pEHAF3XIAcnouFpNH3KprqXK0N3Rh9tP","title":"Introduction","children":[]}],"_tenant":"default","lastModificationAt":1698071181687},"otoroshiTarget":{"otoroshiSettings":"default","authorizedEntities":{"groups":[],"routes":["s_123456"],"services":[]},"apikeyCustomization":{"tags":[],"metadata":{},"readOnly":false,"clientIdOnly":false,"restrictions":{"allowed":[],"enabled":false,"notFound":[],"allowLast":true,"forbidden":[]},"customMetadata":[],"constrainedServicesOnly":false}},"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":false,"customDescription":"fake prod plan (^_-)","integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":null}} diff --git a/daikoku/javascript/src/components/backoffice/apis/TeamApi.tsx b/daikoku/javascript/src/components/backoffice/apis/TeamApi.tsx index 0267a4b42..69e049ec7 100644 --- a/daikoku/javascript/src/components/backoffice/apis/TeamApi.tsx +++ b/daikoku/javascript/src/components/backoffice/apis/TeamApi.tsx @@ -1,32 +1,32 @@ -import React, { useState, useEffect, useRef, useContext } from 'react'; -import { useNavigate, useLocation, useParams, useMatch, Link } from 'react-router-dom'; -import { toastr } from 'react-redux-toastr'; -import Select from 'react-select'; +import { useContext, useEffect, useState } from 'react'; import Plus from 'react-feather/dist/icons/plus'; import { useDispatch, useSelector } from 'react-redux'; +import { toastr } from 'react-redux-toastr'; +import { Link, useLocation, useMatch, useNavigate, useParams } from 'react-router-dom'; +import Select from 'react-select'; -import * as Services from '../../../services'; -import { Can, manage, api as API, Spinner, api, queryClient } from '../../utils'; import { + TeamApiConsumption, + TeamApiDocumentation, TeamApiInfos, TeamApiPost, - TeamApiDocumentation, TeamApiPricings, TeamApiSettings, - TeamPlanConsumption, - TeamApiConsumption, - TeamApiSubscriptions + TeamApiSubscriptions, + TeamPlanConsumption } from '.'; import { ModalContext, useApiBackOffice } from '../../../contexts'; +import * as Services from '../../../services'; +import { api as API, Can, Spinner, api, manage } from '../../utils'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { - setError, I18nContext, + setError, toggleExpertMode, } from '../../../core'; -import { IApi, IImportingDocumentation, isError, IUsagePlan, ResponseError } from '../../../types/api'; import { IState, IStateContext, ITeamSimple } from '../../../types'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { IApi, IUsagePlan, isError } from '../../../types/api'; const reservedCharacters = [';', '/', '?', ':', '@', '&', '=', '+', '$', ',']; type ButtonProps = { @@ -323,7 +323,7 @@ export const TeamApi = (props: { creation: boolean }) => { queryClient.invalidateQueries(['api']); }, getDocumentationPages: () => Services.getAllApiDocumentation(currentTeam._id, api._id, api.currentVersion), - importPages: (pages: Array) => Services.importApiPages(currentTeam._id, api._id, pages, api.currentVersion) + importPages: (pages: Array, linked?: boolean) => Services.importApiPages(currentTeam._id, api._id, pages, api.currentVersion, linked) })} importAuthorized={!!versionsRequest.data && !!versionsRequest.data.length} />)} {tab === 'plans' && ( diff --git a/daikoku/javascript/src/components/backoffice/apis/TeamApiDocumentation.tsx b/daikoku/javascript/src/components/backoffice/apis/TeamApiDocumentation.tsx index 34aed6088..3680c7d97 100644 --- a/daikoku/javascript/src/components/backoffice/apis/TeamApiDocumentation.tsx +++ b/daikoku/javascript/src/components/backoffice/apis/TeamApiDocumentation.tsx @@ -127,14 +127,6 @@ export const TeamApiDocumentation = (props: TeamApiDocumentationProps) => { 'content', ]; - /*useEffect(() => { - Services.getAllApiVersions(props.team._id, params.apiId!) - .then((versions) => - setApiVersions(versions - ) - ); - }, []); */ - const schema = (onSubmitAsset: (page: IDocPage) => void): Schema => { return { title: { @@ -252,7 +244,20 @@ export const TeamApiDocumentation = (props: TeamApiDocumentationProps) => { flow: flow, schema: schema(updatedPage => savePage(updatedPage, page)), value: page, - onSubmit: updatedPage => savePage(updatedPage, page), + noClose: page.linked, + onSubmit: updatedPage => { + if (page.linked) { + confirm({ message: translate('doc.page.linked.confirm') }) + .then(ok => { + if (ok) { + savePage(updatedPage, page) + } + }) + } else { + savePage(updatedPage, page) + } + + }, actionLabel: translate('Save') }) } @@ -362,13 +367,13 @@ export const TeamApiDocumentation = (props: TeamApiDocumentationProps) => { {translate('documentation.add.page.btn.label')} {props.importAuthorized && - - } + + }
diff --git a/daikoku/javascript/src/components/backoffice/apis/TeamApiPricings.tsx b/daikoku/javascript/src/components/backoffice/apis/TeamApiPricings.tsx index 50d2b3d20..604fcb66f 100644 --- a/daikoku/javascript/src/components/backoffice/apis/TeamApiPricings.tsx +++ b/daikoku/javascript/src/components/backoffice/apis/TeamApiPricings.tsx @@ -1923,7 +1923,7 @@ const TeamApiPricingDocumentation = (props: TeamApiPricingDocumentationProps) => props.reloadState() }, getDocumentationPages: () => Services.getAllPlansDocumentation(props.team._id, props.api._humanReadableId, props.api.currentVersion), - importPages: (pages) => Services.importPlanPages(props.team._id, props.api._id, pages, props.api.currentVersion, props.planForEdition._id) + importPages: (pages, linked) => Services.importPlanPages(props.team._id, props.api._id, pages, props.api.currentVersion, props.planForEdition._id, linked) })} /> ) } diff --git a/daikoku/javascript/src/contexts/modals/ApiDocumentationSelectModal.tsx b/daikoku/javascript/src/contexts/modals/ApiDocumentationSelectModal.tsx index 496ce9daa..c38f706ad 100644 --- a/daikoku/javascript/src/contexts/modals/ApiDocumentationSelectModal.tsx +++ b/daikoku/javascript/src/contexts/modals/ApiDocumentationSelectModal.tsx @@ -16,9 +16,9 @@ export const ApiDocumentationSelectModal = (props: IApiDocumentationSelectModalP const pagesQuery = useQuery(['pages'], () => props.getDocumentationPages()) - const importPages = () => { + const importPages = (linked?: boolean) => { // Services.importApiPages(props.teamId, props.api._id, pages.map((p) => p.value), props.api.currentVersion) - props.importPages(pages.flatMap(p => p.value.pageId)) + props.importPages(pages.flatMap(p => p.value.pageId), linked) .then(() => props.onClose()) .then(() => props.close()); } @@ -70,8 +70,11 @@ export const ApiDocumentationSelectModal = (props: IApiDocumentationSelectModalP - + diff --git a/daikoku/javascript/src/contexts/modals/types.ts b/daikoku/javascript/src/contexts/modals/types.ts index 6a0f15a0b..1f2440b23 100644 --- a/daikoku/javascript/src/contexts/modals/types.ts +++ b/daikoku/javascript/src/contexts/modals/types.ts @@ -177,7 +177,7 @@ export interface IApiDocumentationSelectModalProps { api: IApi; getDocumentationPages: () => Promise>; onClose: () => void; - importPages: (pages: Array) => Promise; + importPages: (pages: Array, linked?: boolean) => Promise; } export type TeamSelectorModalProps = { diff --git a/daikoku/javascript/src/locales/en/translation.json b/daikoku/javascript/src/locales/en/translation.json index a93aa784f..4c65f6170 100644 --- a/daikoku/javascript/src/locales/en/translation.json +++ b/daikoku/javascript/src/locales/en/translation.json @@ -1206,6 +1206,7 @@ "doc.page.update.successfull": "page updated successfully", "doc.page.move.error": "You can't move this documentation", "doc.page.error.unknown": "unknown error", + "doc.page.linked.confirm": "This documentation page is used elsewhere. Your change will be visible for all other uses. Are you sure you want to apply those ?", "otoroshi.list.add.label": "Add an Otoroshi instance", "Global stats": "Global stats", "apikeys visibility": "API keys visibility", diff --git a/daikoku/javascript/src/locales/fr/translation.json b/daikoku/javascript/src/locales/fr/translation.json index 96a29139d..5355e834a 100644 --- a/daikoku/javascript/src/locales/fr/translation.json +++ b/daikoku/javascript/src/locales/fr/translation.json @@ -1208,6 +1208,7 @@ "doc.page.update.successfull": "La page a été modifiée avec succès", "doc.page.move.error": "Cette page ne peut être déplacée", "doc.page.error.unknown": "Erreur inconnue", + "doc.page.linked.confirm": "Cette page de documentation est utilisée ailleurs. Votre modification sera visible pout tous les autres usages. Êtes-vous sûr de vouloir les appliquer ?", "otoroshi.list.add.label": "Ajouter une instance Otoroshi", "Global stats": "Statistiques", "apikeys visibility": "Visibilité des clés d'API", diff --git a/daikoku/javascript/src/services/index.ts b/daikoku/javascript/src/services/index.ts index cd67af09c..c9620882f 100644 --- a/daikoku/javascript/src/services/index.ts +++ b/daikoku/javascript/src/services/index.ts @@ -1158,12 +1158,14 @@ export const importApiPages = ( teamId: string, apiId: string, pages: Array, - version: string + version: string, + linked?: boolean ): PromiseWithError => customFetch(`/api/teams/${teamId}/apis/${apiId}/${version}/pages`, { method: 'PUT', body: JSON.stringify({ pages, + linked }), }); @@ -1172,12 +1174,14 @@ export const importPlanPages = ( apiId: string, pages: Array, version: string, - planId: string + planId: string, + linked?: boolean ): PromiseWithError => customFetch(`/api/teams/${teamId}/apis/${apiId}/${version}/plan/${planId}/pages`, { method: 'PUT', body: JSON.stringify({ pages, + linked }), }); diff --git a/daikoku/javascript/src/types/api.ts b/daikoku/javascript/src/types/api.ts index e983e145b..0cb399ca5 100644 --- a/daikoku/javascript/src/types/api.ts +++ b/daikoku/javascript/src/types/api.ts @@ -332,6 +332,7 @@ export interface IDocPage { remoteContentEnabled: boolean; remoteContentUrl: string | null; remoteContentHeaders: object; + linked?: boolean } export interface IOtoroshiApiKey {