Sources/XCMetricsBackendLib/Statistics/Controllers/StatisticsController.swift (43 lines of code) (raw):
// Copyright (c) 2021 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import Fluent
import FluentSQL
import Vapor
/// Controller with endpoints that return statistics for build related data
public struct StatisticsController: RouteCollection {
let repository: StatisticsRepository;
init(repository: StatisticsRepository) {
self.repository = repository;
}
/// Returns the routes supported by this Controller.
/// All the routes are in the `v1/statistics` path
/// - Parameter routes: RoutesBuilder to which the routes will be added
/// - Throws: An `Error` if something goes wrong
public func boot(routes: RoutesBuilder) throws {
routes.get("v1", "statistics", "build", "count", use: buildCounts)
routes.get("v1", "statistics", "build", "status", use: buildStatus)
routes.get("v1", "statistics", "build", "time", use: buildTimes)
}
/// Endpoint that returns a list of `DayCount` which includes
/// the sum of errors and builds during a given day
/// - Method: `GET`
/// - Route: `/v1/statistics/build/count?days=14`
/// - Request parameters
/// - `days`. How many days to include in the past, starting
/// from the current date
///
/// - Response:
/// ```
/// [
/// {
/// "id": "2021-07-14",
/// "builds": 197,
/// "errors": 4,
/// },
/// ...
/// ]
/// ```
public func buildCounts(req: Request) throws -> EventLoopFuture<[DayCount]> {
let (from, to) = try getDatesFromDaysParameter(req)
return self.repository.getDayCounts(from: from, to: to, using: req.eventLoop).flatMap { counts in
self.repository.getCount(day: Date().xcm_truncateTime(), using: req.eventLoop).flatMap { count in
req.eventLoop.makeSucceededFuture(counts + [count])
}
}
}
/// Endpoint that returns the paginated list of `BuildStatusResult`
/// to minimize payload size when many statuses are required
/// - Method: `GET`
/// - Route: `/v1/statistics/build/status?page=1&per=10`
/// - Request parameters
/// - `page`. Optional. Page number to fetch. Default is `1`
/// - `per`. Optional. Number of items to fetch per page. Default is `10`
///
/// - Response:
/// ```
/// {
/// "metadata": {
/// "per": 10,
/// "total": 100,
/// "page": 2
/// },
/// "items": [
/// {
/// "id": "MyMac_34580469-5792-40F3-BEFB-7C5925996F23_1",
/// "buildStatus": "succeeded",
/// },
/// ...
/// ]
/// }
/// ```
public func buildStatus(req: Request) throws -> EventLoopFuture<Page<BuildStatusResult>> {
guard let page = Int(req.query["page"] ?? "1"),
let per = Int(req.query["per"] ?? "10")
else { throw Abort(.badRequest) }
return self.repository.getBuildStatuses(page: page, per: per, using: req.eventLoop)
}
/// Endpoint that returns a list of `DayBuildTime` which includes
/// the build times for a number of procentiles of the builds and the total
/// build time for each day, in seconds.
/// The `durationPX` fields indicate that X % of the builds of the a day
/// had a duration less or equal to the value.
/// - Method: `GET`
/// - Route: `/v1/statistics/build/time?days=14`
/// - Request parameters
/// - `days`. How many days to include in the past, starting
/// from the current date
///
/// - Response:
/// ```
/// [
/// {
/// "id": "2021-07-26",
/// "durationP50": 18473.21,
/// "durationP95": 54431313.32
/// "totalDuration": 10983982398.549,
/// },
/// ...
/// ]
/// ```
public func buildTimes(req: Request) throws -> EventLoopFuture<[DayBuildTime]> {
let (from, to) = try getDatesFromDaysParameter(req)
return self.repository.getDayBuildTimes(from: from, to: to, using: req.eventLoop).flatMap { times in
self.repository.getBuildTime(day: Date().xcm_truncateTime(), using: req.eventLoop).flatMap { time in
req.eventLoop.makeSucceededFuture(times + [time])
}
}
}
// MARK: - Private Methods
private func getDatesFromDaysParameter(_ req: Request) throws -> (from: Date, to: Date) {
guard let days = Int(req.query["days"] ?? "") else { throw Abort(.badRequest) }
guard days > 0 else { throw Abort(.badRequest) }
let from = Calendar.current.date(byAdding: .day, value: -days + 1, to: Date())!.xcm_truncateTime()
let to = Date().xcm_truncateTime()
return (from, to)
}
}