From c5c736a431a20b6282bb56bb85c68bccffadeef2 Mon Sep 17 00:00:00 2001 From: Bria Morgan Date: Thu, 7 Nov 2024 15:59:01 -0500 Subject: [PATCH] add new spendreport api --- core/src/main/resources/swagger/api-docs.yaml | 49 +++ .../webservice/BillingApiServiceV2.scala | 286 ++++++++++-------- 2 files changed, 201 insertions(+), 134 deletions(-) diff --git a/core/src/main/resources/swagger/api-docs.yaml b/core/src/main/resources/swagger/api-docs.yaml index 44e808a2bc..66f313908d 100644 --- a/core/src/main/resources/swagger/api-docs.yaml +++ b/core/src/main/resources/swagger/api-docs.yaml @@ -450,6 +450,55 @@ paths: $ref: '#/components/schemas/ErrorReport' 500: $ref: '#/components/responses/RawlsInternalError' + /api/billing/v2/spendReport: + get: + tags: + - billing_v2 + summary: get spend report for all workspaces user has owner access to + description: get spend report for all workspaces user has owner access to + operationId: getSpendReportAllWorkspaces + parameters: + - name: startDate + in: query + description: start date of report (YYYY-MM-DD). Data included in report will start at 12 AM UTC on this date. + required: true + schema: + type: string + format: date + - name: endDate + in: query + description: end date of report (YYYY-MM-DD). Data included in report will end at 11:59 PM UTC on this date. + required: true + schema: + type: string + format: date + responses: + 200: + description: Success + content: + 'application/json': + schema: + $ref: '#/components/schemas/SpendReport' + 400: + description: invalid spend report parameters + content: + 'application/json': + schema: + $ref: '#/components/schemas/ErrorReport' + 403: + description: You must be a project owner to view the spend report of a project + content: + 'application/json': + schema: + $ref: '#/components/schemas/ErrorReport' + 404: + description: The specified billing project could not be found + content: + 'application/json': + schema: + $ref: '#/components/schemas/ErrorReport' + 500: + $ref: '#/components/responses/RawlsInternalError' /api/billing/v2/{projectId}/spendReportConfiguration: get: tags: diff --git a/core/src/main/scala/org/broadinstitute/dsde/rawls/webservice/BillingApiServiceV2.scala b/core/src/main/scala/org/broadinstitute/dsde/rawls/webservice/BillingApiServiceV2.scala index b1204568b4..ae44d9f317 100644 --- a/core/src/main/scala/org/broadinstitute/dsde/rawls/webservice/BillingApiServiceV2.scala +++ b/core/src/main/scala/org/broadinstitute/dsde/rawls/webservice/BillingApiServiceV2.scala @@ -59,177 +59,195 @@ trait BillingApiServiceV2 extends UserInfoDirectives { requireUserInfo(Option(otelContext)) { userInfo => val ctx = RawlsRequestContext(userInfo, Option(otelContext)) pathPrefix("billing" / "v2") { - - pathPrefix(Segment) { projectId => - pathEnd { + pathPrefix("spendReport") { + pathEndOrSingleSlash { get { - complete { - import spray.json._ - userServiceConstructor(ctx).getBillingProject(RawlsBillingProjectName(projectId)).map { - case Some(projectResponse) => StatusCodes.OK -> Option(projectResponse).toJson - case None => StatusCodes.NotFound -> Option(StatusCodes.NotFound.defaultMessage).toJson + parameters( + "startDate".as[DateTime], + "endDate".as[DateTime] + ) { (startDate, endDate) => + complete { + spendReportingConstructor(ctx).getSpendForAllWorkspaces( + startDate, + endDate.plusDays(1).minusMillis(1) + ) } } - } ~ - delete { + } + } + } ~ + pathPrefix(Segment) { projectId => + pathEnd { + get { complete { - billingProjectOrchestratorConstructor(ctx) - .deleteBillingProjectV2(RawlsBillingProjectName(projectId)) - .map(_ => StatusCodes.NoContent) + import spray.json._ + userServiceConstructor(ctx).getBillingProject(RawlsBillingProjectName(projectId)).map { + case Some(projectResponse) => StatusCodes.OK -> Option(projectResponse).toJson + case None => StatusCodes.NotFound -> Option(StatusCodes.NotFound.defaultMessage).toJson + } } - } - } ~ - pathPrefix("spendReport") { - pathEndOrSingleSlash { - get { - parameters( - "startDate".as[DateTime], - "endDate".as[DateTime], - "aggregationKey" - .as[SpendReportingAggregationKeyWithSub](aggregationKeyParameterUnmarshaller) - .repeated - ) { (startDate, endDate, aggregationKeyParameters) => - complete { - spendReportingConstructor(ctx).getSpendForBillingProject( - RawlsBillingProjectName(projectId), - startDate, - endDate.plusDays(1).minusMillis(1), - aggregationKeyParameters.toSet - ) - } + } ~ + delete { + complete { + billingProjectOrchestratorConstructor(ctx) + .deleteBillingProjectV2(RawlsBillingProjectName(projectId)) + .map(_ => StatusCodes.NoContent) } } - } } ~ - pathPrefix("spendReportConfiguration") { - pathEnd { - put { - entity(as[BillingProjectSpendConfiguration]) { spendConfiguration => - complete { - userServiceConstructor(ctx) - .setBillingProjectSpendConfiguration(RawlsBillingProjectName(projectId), spendConfiguration) - .map(_ => StatusCodes.NoContent) + pathPrefix("spendReport") { + pathEndOrSingleSlash { + get { + parameters( + "startDate".as[DateTime], + "endDate".as[DateTime], + "aggregationKey" + .as[SpendReportingAggregationKeyWithSub](aggregationKeyParameterUnmarshaller) + .repeated + ) { (startDate, endDate, aggregationKeyParameters) => + complete { + spendReportingConstructor(ctx).getSpendForBillingProject( + RawlsBillingProjectName(projectId), + startDate, + endDate.plusDays(1).minusMillis(1), + aggregationKeyParameters.toSet + ) + } } } - } ~ - delete { - complete { - userServiceConstructor(ctx) - .clearBillingProjectSpendConfiguration(RawlsBillingProjectName(projectId)) - .map(_ => StatusCodes.NoContent) + } + } ~ + pathPrefix("spendReportConfiguration") { + pathEnd { + put { + entity(as[BillingProjectSpendConfiguration]) { spendConfiguration => + complete { + userServiceConstructor(ctx) + .setBillingProjectSpendConfiguration(RawlsBillingProjectName(projectId), spendConfiguration) + .map(_ => StatusCodes.NoContent) + } } } ~ - get { - complete { - userServiceConstructor(ctx) - .getBillingProjectSpendConfiguration(RawlsBillingProjectName(projectId)) - .map { - case Some(config) => StatusCodes.OK -> Option(config) - case None => StatusCodes.NoContent -> None - } + delete { + complete { + userServiceConstructor(ctx) + .clearBillingProjectSpendConfiguration(RawlsBillingProjectName(projectId)) + .map(_ => StatusCodes.NoContent) + } + } ~ + get { + complete { + userServiceConstructor(ctx) + .getBillingProjectSpendConfiguration(RawlsBillingProjectName(projectId)) + .map { + case Some(config) => StatusCodes.OK -> Option(config) + case None => StatusCodes.NoContent -> None + } + } } - } - } - } ~ - pathPrefix("billingAccount") { - pathEnd { - put { - entity(as[UpdateRawlsBillingAccountRequest]) { updateProjectRequest => - complete { - userServiceConstructor(ctx) - .updateBillingProjectBillingAccount(RawlsBillingProjectName(projectId), updateProjectRequest) - .map { + } + } ~ + pathPrefix("billingAccount") { + pathEnd { + put { + entity(as[UpdateRawlsBillingAccountRequest]) { updateProjectRequest => + complete { + userServiceConstructor(ctx) + .updateBillingProjectBillingAccount(RawlsBillingProjectName(projectId), updateProjectRequest) + .map { + case Some(billingProject) => StatusCodes.OK -> Option(billingProject) + case None => StatusCodes.NoContent -> None + } + } + } + } ~ + delete { + complete { + userServiceConstructor(ctx).deleteBillingAccount(RawlsBillingProjectName(projectId)).map { case Some(billingProject) => StatusCodes.OK -> Option(billingProject) case None => StatusCodes.NoContent -> None } + } } - } - } ~ - delete { + } + } ~ + pathPrefix("members") { + pathEnd { + get { complete { - userServiceConstructor(ctx).deleteBillingAccount(RawlsBillingProjectName(projectId)).map { - case Some(billingProject) => StatusCodes.OK -> Option(billingProject) - case None => StatusCodes.NoContent -> None + userServiceConstructor(ctx).getBillingProjectMembers(RawlsBillingProjectName(projectId)) + } + } ~ + patch { + parameter(Symbol("inviteUsersNotFound").?) { inviteUsersNotFound => + entity(as[BatchProjectAccessUpdate]) { batchProjectAccessUpdate => + complete { + userServiceConstructor(ctx) + .batchUpdateBillingProjectMembers(RawlsBillingProjectName(projectId), + batchProjectAccessUpdate, + inviteUsersNotFound.getOrElse("false").toBoolean + ) + .map(_ => StatusCodes.NoContent -> None) + } + } } } - } - } - } ~ - pathPrefix("members") { - pathEnd { - get { - complete { - userServiceConstructor(ctx).getBillingProjectMembers(RawlsBillingProjectName(projectId)) - } } ~ - patch { - parameter(Symbol("inviteUsersNotFound").?) { inviteUsersNotFound => - entity(as[BatchProjectAccessUpdate]) { batchProjectAccessUpdate => + // these routes are for adding/removing users from projects + path(Segment / Segment) { (workbenchRole, userEmail) => + put { + complete { + userServiceConstructor(ctx) + .addUserToBillingProjectV2(RawlsBillingProjectName(projectId), + ProjectAccessUpdate(userEmail, + ProjectRoles.withName(workbenchRole) + ) + ) + .map(_ => StatusCodes.OK) + } + } ~ + delete { complete { userServiceConstructor(ctx) - .batchUpdateBillingProjectMembers(RawlsBillingProjectName(projectId), - batchProjectAccessUpdate, - inviteUsersNotFound.getOrElse("false").toBoolean + .removeUserFromBillingProjectV2(RawlsBillingProjectName(projectId), + ProjectAccessUpdate(userEmail, + ProjectRoles.withName(workbenchRole) + ) ) - .map(_ => StatusCodes.NoContent -> None) + .map(_ => StatusCodes.OK) } } - } } } ~ - // these routes are for adding/removing users from projects - path(Segment / Segment) { (workbenchRole, userEmail) => - put { + pathPrefix("bucketMigration") { + val billingProjectName = RawlsBillingProjectName(projectId) + pathEndOrSingleSlash { + post { complete { - userServiceConstructor(ctx) - .addUserToBillingProjectV2(RawlsBillingProjectName(projectId), - ProjectAccessUpdate(userEmail, ProjectRoles.withName(workbenchRole)) - ) - .map(_ => StatusCodes.OK) + bucketMigrationServiceConstructor(ctx) + .migrateWorkspaceBucketsInBillingProject(billingProjectName) + .map(StatusCodes.Created -> _) } } ~ - delete { + get { complete { - userServiceConstructor(ctx) - .removeUserFromBillingProjectV2(RawlsBillingProjectName(projectId), - ProjectAccessUpdate(userEmail, - ProjectRoles.withName(workbenchRole) - ) - ) - .map(_ => StatusCodes.OK) + bucketMigrationServiceConstructor(ctx) + .getBucketMigrationAttemptsForBillingProject(billingProjectName) + .map(ms => StatusCodes.OK -> ms) } } - } - } ~ - pathPrefix("bucketMigration") { - val billingProjectName = RawlsBillingProjectName(projectId) - pathEndOrSingleSlash { - post { - complete { - bucketMigrationServiceConstructor(ctx) - .migrateWorkspaceBucketsInBillingProject(billingProjectName) - .map(StatusCodes.Created -> _) - } } ~ - get { - complete { - bucketMigrationServiceConstructor(ctx) - .getBucketMigrationAttemptsForBillingProject(billingProjectName) - .map(ms => StatusCodes.OK -> ms) - } - } - } ~ - path("progress") { - get { - complete { - bucketMigrationServiceConstructor(ctx) - .getBucketMigrationProgressForBillingProject(billingProjectName) - .map(StatusCodes.OK -> _) + path("progress") { + get { + complete { + bucketMigrationServiceConstructor(ctx) + .getBucketMigrationProgressForBillingProject(billingProjectName) + .map(StatusCodes.OK -> _) + } } } - } - } - } ~ + } + } ~ pathEnd { get { complete {