Send cache targets hits analytics metadata (#4429)
* Add cache hit rate analytics * Move metadata to command_event * Add cache metadata to command run * Show run cache analytics
This commit is contained in:
parent
dd5d42a06d
commit
341047e210
|
@ -10,6 +10,15 @@
|
|||
"version": "4.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "AnyCodable",
|
||||
"repositoryURL": "https://github.com/Flight-School/AnyCodable",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "f9fda69a7b704d46fb5123005f2f7e43dbb8a0fa",
|
||||
"version": "0.6.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Checksum",
|
||||
"repositoryURL": "https://github.com/rnine/Checksum.git",
|
||||
|
|
|
@ -59,6 +59,7 @@ let package = Package(
|
|||
.package(url: "https://github.com/apple/swift-tools-support-core.git", .upToNextMinor(from: "0.2.5")),
|
||||
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.0.0")),
|
||||
.package(url: "https://github.com/fortmarek/ZIPFoundation.git", .revision("9dbe729b90202c19d0fe1010f1430fa75a576cd3")),
|
||||
.package(url: "https://github.com/Flight-School/AnyCodable", from: "0.6.0"),
|
||||
.package(url: "https://github.com/tuist/GraphViz.git", .branch("tuist")),
|
||||
.package(url: "https://github.com/SwiftGen/SwiftGen", .exact("6.5.0")),
|
||||
.package(url: "https://github.com/SwiftGen/StencilSwiftKit.git", .upToNextMajor(from: "2.8.0")),
|
||||
|
@ -608,6 +609,7 @@ let package = Package(
|
|||
.target(
|
||||
name: "TuistAnalytics",
|
||||
dependencies: [
|
||||
.byName(name: "AnyCodable"),
|
||||
"TuistAsyncQueue",
|
||||
"TuistCloud",
|
||||
"TuistCore",
|
||||
|
|
|
@ -379,6 +379,7 @@ func targets() -> [Target] {
|
|||
.target(name: "TuistCore"),
|
||||
.target(name: "TuistGraph"),
|
||||
.target(name: "TuistLoader"),
|
||||
.external(name: "AnyCodable"),
|
||||
],
|
||||
testDependencies: [
|
||||
.target(name: "TuistSupportTesting"),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import AnyCodable
|
||||
import Foundation
|
||||
import TuistCore
|
||||
|
||||
|
@ -6,7 +7,7 @@ import TuistCore
|
|||
public struct CommandEvent: Codable, Equatable, AsyncQueueEvent {
|
||||
public let name: String
|
||||
public let subcommand: String?
|
||||
public let params: [String: String]
|
||||
public let params: [String: AnyCodable]
|
||||
public let commandArguments: [String]
|
||||
public let durationInMs: Int
|
||||
public let clientId: String
|
||||
|
@ -37,7 +38,7 @@ public struct CommandEvent: Codable, Equatable, AsyncQueueEvent {
|
|||
public init(
|
||||
name: String,
|
||||
subcommand: String?,
|
||||
params: [String: String],
|
||||
params: [String: AnyCodable],
|
||||
commandArguments: [String],
|
||||
durationInMs: Int,
|
||||
clientId: String,
|
||||
|
|
|
@ -18,6 +18,7 @@ public final class Cache: CacheStoring {
|
|||
// MARK: - CacheStoring
|
||||
|
||||
public func exists(name: String, hash: String) async throws -> Bool {
|
||||
CacheAnalytics.cacheableTargets.append(name)
|
||||
for storage in storages {
|
||||
if try await storage.exists(name: name, hash: hash) {
|
||||
return true
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import Foundation
|
||||
import TuistSupport
|
||||
|
||||
public enum CacheAnalytics {
|
||||
@Atomic
|
||||
public static var localCacheTargetsHits: Set<String> = []
|
||||
@Atomic
|
||||
public static var remoteCacheTargetsHits: Set<String> = []
|
||||
@Atomic
|
||||
public static var cacheableTargets: [String] = []
|
||||
}
|
|
@ -37,9 +37,13 @@ public final class CacheLocalStorage: CacheStoring {
|
|||
|
||||
// MARK: - CacheStoring
|
||||
|
||||
public func exists(name _: String, hash: String) throws -> Bool {
|
||||
public func exists(name: String, hash: String) throws -> Bool {
|
||||
let hashFolder = cacheDirectory.appending(component: hash)
|
||||
return lookupCompiledArtifact(directory: hashFolder) != nil
|
||||
let exists = lookupCompiledArtifact(directory: hashFolder) != nil
|
||||
if exists {
|
||||
CacheAnalytics.localCacheTargetsHits.insert(name)
|
||||
}
|
||||
return exists
|
||||
}
|
||||
|
||||
public func fetch(name _: String, hash: String) throws -> AbsolutePath {
|
||||
|
|
|
@ -68,7 +68,11 @@ public final class CacheRemoteStorage: CacheStoring {
|
|||
let successRange = 200 ..< 300
|
||||
let resource = try cloudCacheResourceFactory.existsResource(name: name, hash: hash)
|
||||
let (_, response) = try await cloudClient.request(resource)
|
||||
return successRange.contains(response.statusCode)
|
||||
let exists = successRange.contains(response.statusCode)
|
||||
if exists {
|
||||
CacheAnalytics.remoteCacheTargetsHits.insert(name)
|
||||
}
|
||||
return exists
|
||||
} catch {
|
||||
if case let HTTPRequestDispatcherError.serverSideError(_, response) = error, response.statusCode == 404 {
|
||||
return false
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import AnyCodable
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
import TuistCache
|
||||
|
||||
struct GenerateCommand: AsyncParsableCommand, HasTrackableParameters {
|
||||
static var analyticsDelegate: TrackableParametersDelegate?
|
||||
|
@ -51,13 +53,6 @@ struct GenerateCommand: AsyncParsableCommand, HasTrackableParameters {
|
|||
var ignoreCache: Bool = false
|
||||
|
||||
func runAsync() async throws {
|
||||
GenerateCommand.analyticsDelegate?.willRun(withParameters: [
|
||||
"no_open": String(noOpen),
|
||||
"xcframeworks": String(xcframeworks),
|
||||
"no_cache": String(ignoreCache),
|
||||
"n_targets": String(sources.count),
|
||||
])
|
||||
|
||||
try await GenerateService().run(
|
||||
path: path,
|
||||
sources: Set(sources),
|
||||
|
@ -66,5 +61,16 @@ struct GenerateCommand: AsyncParsableCommand, HasTrackableParameters {
|
|||
profile: profile,
|
||||
ignoreCache: ignoreCache
|
||||
)
|
||||
GenerateCommand.analyticsDelegate?.addParameters(
|
||||
[
|
||||
"no_open": AnyCodable(noOpen),
|
||||
"xcframeworks": AnyCodable(xcframeworks),
|
||||
"no_cache": AnyCodable(ignoreCache),
|
||||
"n_targets": AnyCodable(sources.count),
|
||||
"cacheable_targets": AnyCodable(CacheAnalytics.cacheableTargets),
|
||||
"local_cache_target_hits": AnyCodable(CacheAnalytics.localCacheTargetsHits),
|
||||
"remote_cache_target_hits": AnyCodable(CacheAnalytics.remoteCacheTargetsHits),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import AnyCodable
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
import GraphViz
|
||||
|
@ -58,12 +59,14 @@ struct GraphCommand: AsyncParsableCommand, HasTrackableParameters {
|
|||
var outputPath: String?
|
||||
|
||||
func runAsync() async throws {
|
||||
GraphCommand.analyticsDelegate?.willRun(withParameters: [
|
||||
"format": format.rawValue,
|
||||
"algorithm": layoutAlgorithm.rawValue,
|
||||
"skip_external_dependencies": String(skipExternalDependencies),
|
||||
"skip_test_targets": String(skipExternalDependencies),
|
||||
])
|
||||
GraphCommand.analyticsDelegate?.addParameters(
|
||||
[
|
||||
"format": AnyCodable(format.rawValue),
|
||||
"algorithm": AnyCodable(layoutAlgorithm.rawValue),
|
||||
"skip_external_dependencies": AnyCodable(skipExternalDependencies),
|
||||
"skip_test_targets": AnyCodable(skipExternalDependencies),
|
||||
]
|
||||
)
|
||||
try await GraphService().run(
|
||||
format: format,
|
||||
layoutAlgorithm: layoutAlgorithm,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import AnyCodable
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
import TSCBasic
|
||||
|
@ -73,7 +74,11 @@ struct InitCommand: ParsableCommand, HasTrackableParameters {
|
|||
}
|
||||
|
||||
func run() throws {
|
||||
InitCommand.analyticsDelegate?.willRun(withParameters: ["platform": platform ?? "unknown"])
|
||||
InitCommand.analyticsDelegate?.addParameters(
|
||||
[
|
||||
"platform": AnyCodable(platform ?? "unknown"),
|
||||
]
|
||||
)
|
||||
try InitService().run(
|
||||
name: name,
|
||||
platform: platform,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import AnyCodable
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
import TuistAsyncQueue
|
||||
|
@ -7,7 +8,7 @@ import TuistSupport
|
|||
public struct TrackableCommandInfo {
|
||||
let name: String
|
||||
let subcommand: String?
|
||||
let parameters: [String: String]
|
||||
let parameters: [String: AnyCodable]
|
||||
let commandArguments: [String]
|
||||
let durationInMs: Int
|
||||
}
|
||||
|
@ -16,7 +17,7 @@ public struct TrackableCommandInfo {
|
|||
public class TrackableCommand: TrackableParametersDelegate {
|
||||
private var command: ParsableCommand
|
||||
private let clock: Clock
|
||||
private var trackedParameters: [String: String] = [:]
|
||||
private var trackedParameters: [String: AnyCodable] = [:]
|
||||
private let commandArguments: [String]
|
||||
private let commandEventFactory: CommandEventFactory
|
||||
private let asyncQueue: AsyncQueuing
|
||||
|
@ -60,8 +61,11 @@ public class TrackableCommand: TrackableParametersDelegate {
|
|||
try asyncQueue.dispatch(event: commandEvent)
|
||||
}
|
||||
|
||||
func willRun(withParameters parameters: [String: String]) {
|
||||
trackedParameters = parameters
|
||||
func addParameters(_ parameters: [String: AnyCodable]) {
|
||||
trackedParameters.merge(
|
||||
parameters,
|
||||
uniquingKeysWith: { _, newKey in newKey }
|
||||
)
|
||||
}
|
||||
|
||||
private func extractCommandName(from configuration: CommandConfiguration) -> (name: String, subcommand: String?) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import AnyCodable
|
||||
import Foundation
|
||||
|
||||
/// Commands that conform to `HasTrackableParameters` can report extra parameters that are only known at runtime
|
||||
|
@ -8,5 +9,5 @@ protocol HasTrackableParameters {
|
|||
/// `TrackableParametersDelegate` contains the callback that should be called
|
||||
/// before running a command, with extra parameters that are only known at runtime
|
||||
protocol TrackableParametersDelegate: AnyObject {
|
||||
func willRun(withParameters: [String: String])
|
||||
func addParameters(_ parameters: [String: AnyCodable])
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import AnyCodable
|
||||
import Foundation
|
||||
@testable import TuistAnalytics
|
||||
|
||||
|
@ -5,7 +6,7 @@ extension CommandEvent {
|
|||
static func test(
|
||||
name: String = "generate",
|
||||
subcommand: String? = nil,
|
||||
params: [String: String] = [:],
|
||||
params: [String: AnyCodable] = [:],
|
||||
commandArguments: [String] = [],
|
||||
durationInMs: Int = 20,
|
||||
clientId: String = "123",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import AnyCodable
|
||||
import ArgumentParser
|
||||
import Combine
|
||||
import Foundation
|
||||
|
@ -38,7 +39,7 @@ final class TrackableCommandTests: TuistTestCase {
|
|||
func test_whenParamsHaveFlagTrue_dispatchesEventWithExpectedParameters() async throws {
|
||||
// Given
|
||||
makeSubject(flag: true)
|
||||
let expectedParams = ["flag": "true"]
|
||||
let expectedParams: [String: AnyCodable] = ["flag": true]
|
||||
|
||||
// When
|
||||
try await subject.run()
|
||||
|
@ -53,7 +54,7 @@ final class TrackableCommandTests: TuistTestCase {
|
|||
func test_whenParamsHaveFlagFalse_dispatchesEventWithExpectedParameters() async throws {
|
||||
// Given
|
||||
makeSubject(flag: false)
|
||||
let expectedParams = ["flag": "false"]
|
||||
let expectedParams: [String: AnyCodable] = ["flag": false]
|
||||
// When
|
||||
try await subject.run()
|
||||
|
||||
|
@ -75,6 +76,6 @@ private struct TestCommand: ParsableCommand, HasTrackableParameters {
|
|||
static var analyticsDelegate: TrackableParametersDelegate?
|
||||
|
||||
func run() throws {
|
||||
TestCommand.analyticsDelegate?.willRun(withParameters: ["flag": String(flag)])
|
||||
TestCommand.analyticsDelegate?.addParameters(["flag": AnyCodable(flag)])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ let dependencies = Dependencies(
|
|||
url: "https://github.com/fortmarek/ZIPFoundation.git",
|
||||
.revision("9dbe729b90202c19d0fe1010f1430fa75a576cd3")
|
||||
),
|
||||
.package(url: "https://github.com/Flight-School/AnyCodable", from: "0.6.0"),
|
||||
.package(url: "https://github.com/rnine/Checksum.git", .upToNextMajor(from: "1.0.2")),
|
||||
.package(url: "https://github.com/stencilproject/Stencil.git", .upToNextMajor(from: "0.14.1")),
|
||||
.package(url: "https://github.com/SwiftGen/StencilSwiftKit.git", .upToNextMajor(from: "2.8.0")),
|
||||
|
|
|
@ -10,6 +10,15 @@
|
|||
"version": "4.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "AnyCodable",
|
||||
"repositoryURL": "https://github.com/Flight-School/AnyCodable",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "f9fda69a7b704d46fb5123005f2f7e43dbb8a0fa",
|
||||
"version": "0.6.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Checksum",
|
||||
"repositoryURL": "https://github.com/rnine/Checksum.git",
|
||||
|
|
|
@ -12,7 +12,10 @@ class AnalyticsController < APIController
|
|||
client_id: params[:client_id],
|
||||
tuist_version: params[:tuist_version],
|
||||
swift_version: params[:swift_version],
|
||||
macos_version: params[:macos_version]
|
||||
macos_version: params[:macos_version],
|
||||
cacheable_targets: params[:params][:cacheable_targets],
|
||||
local_cache_target_hits: params[:params][:local_cache_target_hits],
|
||||
remote_cache_target_hits: params[:params][:remote_cache_target_hits]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ import { observer } from 'mobx-react-lite';
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import CommandEventDetailPageStore from './CommandEventDetailPageStore';
|
||||
import CacheCardSection from './components/CacheCardSection';
|
||||
|
||||
const CommandEventDetailPage = observer(() => {
|
||||
const client = useApolloClient();
|
||||
|
@ -65,6 +66,26 @@ const CommandEventDetailPage = observer(() => {
|
|||
</Stack>
|
||||
</Stack>
|
||||
</Card.Section>
|
||||
<CacheCardSection
|
||||
cacheableTargets={
|
||||
commandEventDetailPageStore.commandEventDetail
|
||||
.cacheableTargets
|
||||
}
|
||||
localCacheTargetHits={
|
||||
commandEventDetailPageStore.commandEventDetail
|
||||
.localCacheTargetHits
|
||||
}
|
||||
remoteCacheTargetHits={
|
||||
commandEventDetailPageStore.commandEventDetail
|
||||
.remoteCacheTargetHits
|
||||
}
|
||||
cacheTargetMisses={
|
||||
commandEventDetailPageStore.cacheTargetMisses
|
||||
}
|
||||
cacheTargetHitRate={
|
||||
commandEventDetailPageStore.cacheTargetHitRate
|
||||
}
|
||||
/>
|
||||
<Card.Section title="Environment">
|
||||
<Stack vertical>
|
||||
<Stack>
|
||||
|
|
|
@ -18,6 +18,51 @@ class CommandEventDetailPageStore {
|
|||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
get cacheTargetHitRate(): string {
|
||||
if (this.commandEventDetail == null) {
|
||||
return '';
|
||||
}
|
||||
const {
|
||||
cacheableTargets,
|
||||
localCacheTargetHits,
|
||||
remoteCacheTargetHits,
|
||||
} = this.commandEventDetail;
|
||||
if (
|
||||
cacheableTargets === null ||
|
||||
localCacheTargetHits === null ||
|
||||
remoteCacheTargetHits === null
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const cacheTargetHitRate = Math.ceil(
|
||||
((localCacheTargetHits.length + remoteCacheTargetHits.length) /
|
||||
cacheableTargets.length) *
|
||||
100,
|
||||
);
|
||||
|
||||
return `${cacheTargetHitRate} %`;
|
||||
}
|
||||
|
||||
get cacheTargetMisses(): string[] {
|
||||
if (this.commandEventDetail?.cacheableTargets == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.commandEventDetail.cacheableTargets.filter(
|
||||
(target) => {
|
||||
return !(
|
||||
this.commandEventDetail?.localCacheTargetHits?.includes(
|
||||
target,
|
||||
) ||
|
||||
this.commandEventDetail?.remoteCacheTargetHits?.includes(
|
||||
target,
|
||||
)
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async load(commandEventId: string) {
|
||||
const { data } = await this.client.query<CommandEventQuery>({
|
||||
query: CommandEventDocument,
|
||||
|
|
|
@ -32,6 +32,9 @@ describe('CommandEventDetailPageStore', () => {
|
|||
id: 'command-event-id',
|
||||
name: 'generate',
|
||||
subcommand: null,
|
||||
cacheableTargets: ['Target1', 'Target2', 'Target3', 'Target4'],
|
||||
localCacheTargetHits: ['Target2', 'Target4'],
|
||||
remoteCacheTargetHits: ['Target3'],
|
||||
};
|
||||
const commandEventDetailFragment = {
|
||||
clientId: commandEventDetail.clientId,
|
||||
|
@ -44,6 +47,9 @@ describe('CommandEventDetailPageStore', () => {
|
|||
id: commandEventDetail.id,
|
||||
name: commandEventDetail.name,
|
||||
subcommand: commandEventDetail.subcommand,
|
||||
cacheableTargets: commandEventDetail.cacheableTargets,
|
||||
localCacheTargetHits: commandEventDetail.localCacheTargetHits,
|
||||
remoteCacheTargetHits: commandEventDetail.remoteCacheTargetHits,
|
||||
__typename: 'CommandEvent',
|
||||
} as CommandEventDetailFragment;
|
||||
client.query.mockResolvedValueOnce({
|
||||
|
@ -59,5 +65,11 @@ describe('CommandEventDetailPageStore', () => {
|
|||
expect(commandEventDetailPageStore.commandEventDetail).toEqual(
|
||||
commandEventDetail,
|
||||
);
|
||||
expect(commandEventDetailPageStore.cacheTargetHitRate).toEqual(
|
||||
'75 %',
|
||||
);
|
||||
expect(commandEventDetailPageStore.cacheTargetMisses).toEqual([
|
||||
'Target1',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import React from 'react';
|
||||
import { Card, TextStyle, Stack } from '@shopify/polaris';
|
||||
|
||||
interface CacheCardSectionProps {
|
||||
cacheableTargets: string[] | null;
|
||||
localCacheTargetHits: string[] | null;
|
||||
remoteCacheTargetHits: string[] | null;
|
||||
cacheTargetMisses: string[];
|
||||
cacheTargetHitRate: string;
|
||||
}
|
||||
|
||||
const CacheCardSection = ({
|
||||
cacheableTargets,
|
||||
localCacheTargetHits,
|
||||
remoteCacheTargetHits,
|
||||
cacheTargetMisses,
|
||||
cacheTargetHitRate,
|
||||
}: CacheCardSectionProps) => {
|
||||
if (
|
||||
cacheableTargets === null ||
|
||||
localCacheTargetHits === null ||
|
||||
remoteCacheTargetHits === null
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card.Section title="Cache">
|
||||
<Stack vertical>
|
||||
<Stack>
|
||||
<TextStyle variation="subdued">Cacheable targets</TextStyle>
|
||||
<TextStyle>{`Total count: ${cacheableTargets.length}`}</TextStyle>
|
||||
</Stack>
|
||||
<TextStyle variation="code">
|
||||
{cacheableTargets.join(' ')}
|
||||
</TextStyle>
|
||||
<Stack>
|
||||
<TextStyle variation="subdued">
|
||||
Targets from local cache
|
||||
</TextStyle>
|
||||
<TextStyle>{`Total count: ${localCacheTargetHits.length}`}</TextStyle>
|
||||
{localCacheTargetHits.length > 0 && (
|
||||
<TextStyle variation="code">
|
||||
{localCacheTargetHits.join(' ')}
|
||||
</TextStyle>
|
||||
)}
|
||||
</Stack>
|
||||
<Stack>
|
||||
<TextStyle variation="subdued">
|
||||
Targets from remote cache
|
||||
</TextStyle>
|
||||
<TextStyle>{`Total count: ${remoteCacheTargetHits.length}`}</TextStyle>
|
||||
</Stack>
|
||||
{remoteCacheTargetHits.length > 0 && (
|
||||
<TextStyle variation="code">
|
||||
{remoteCacheTargetHits.join(' ')}
|
||||
</TextStyle>
|
||||
)}
|
||||
<Stack>
|
||||
<TextStyle variation="subdued">
|
||||
Cache targets missed
|
||||
</TextStyle>
|
||||
<TextStyle>{`Total count: ${cacheTargetMisses.length}`}</TextStyle>
|
||||
</Stack>
|
||||
{cacheTargetMisses.length > 0 && (
|
||||
<TextStyle variation="code">
|
||||
{cacheTargetMisses.join(' ')}
|
||||
</TextStyle>
|
||||
)}
|
||||
<Stack>
|
||||
<TextStyle variation="subdued">
|
||||
Total cache hit rate
|
||||
</TextStyle>
|
||||
<TextStyle>{cacheTargetHitRate}</TextStyle>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card.Section>
|
||||
);
|
||||
};
|
||||
|
||||
export default CacheCardSection;
|
|
@ -9,4 +9,7 @@ fragment CommandEventDetail on CommandEvent {
|
|||
swiftVersion
|
||||
macosVersion
|
||||
createdAt
|
||||
cacheableTargets
|
||||
localCacheTargetHits
|
||||
remoteCacheTargetHits
|
||||
}
|
||||
|
|
|
@ -64,13 +64,16 @@ export type CommandAverage = {
|
|||
|
||||
export type CommandEvent = {
|
||||
__typename?: 'CommandEvent';
|
||||
cacheableTargets?: Maybe<Array<Scalars['String']>>;
|
||||
clientId: Scalars['String'];
|
||||
commandArguments: Scalars['String'];
|
||||
createdAt: Scalars['ISO8601DateTime'];
|
||||
duration: Scalars['Int'];
|
||||
id: Scalars['ID'];
|
||||
localCacheTargetHits?: Maybe<Array<Scalars['String']>>;
|
||||
macosVersion: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
remoteCacheTargetHits?: Maybe<Array<Scalars['String']>>;
|
||||
subcommand?: Maybe<Scalars['String']>;
|
||||
swiftVersion: Scalars['String'];
|
||||
tuistVersion: Scalars['String'];
|
||||
|
@ -431,14 +434,14 @@ export type CommandAveragesQuery = { __typename?: 'Query', commandAverages: Arra
|
|||
|
||||
export type CommandEventFragment = { __typename?: 'CommandEvent', id: string, commandArguments: string, duration: number, createdAt: string };
|
||||
|
||||
export type CommandEventDetailFragment = { __typename?: 'CommandEvent', id: string, name: string, subcommand?: string | null, commandArguments: string, duration: number, clientId: string, tuistVersion: string, swiftVersion: string, macosVersion: string, createdAt: string };
|
||||
export type CommandEventDetailFragment = { __typename?: 'CommandEvent', id: string, name: string, subcommand?: string | null, commandArguments: string, duration: number, clientId: string, tuistVersion: string, swiftVersion: string, macosVersion: string, createdAt: string, cacheableTargets?: Array<string> | null, localCacheTargetHits?: Array<string> | null, remoteCacheTargetHits?: Array<string> | null };
|
||||
|
||||
export type CommandEventQueryVariables = Exact<{
|
||||
commandEventId: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type CommandEventQuery = { __typename?: 'Query', commandEvent: { __typename?: 'CommandEvent', id: string, name: string, subcommand?: string | null, commandArguments: string, duration: number, clientId: string, tuistVersion: string, swiftVersion: string, macosVersion: string, createdAt: string } };
|
||||
export type CommandEventQuery = { __typename?: 'Query', commandEvent: { __typename?: 'CommandEvent', id: string, name: string, subcommand?: string | null, commandArguments: string, duration: number, clientId: string, tuistVersion: string, swiftVersion: string, macosVersion: string, createdAt: string, cacheableTargets?: Array<string> | null, localCacheTargetHits?: Array<string> | null, remoteCacheTargetHits?: Array<string> | null } };
|
||||
|
||||
export type CommandEventsQueryVariables = Exact<{
|
||||
projectId: Scalars['ID'];
|
||||
|
@ -585,6 +588,9 @@ export const CommandEventDetailFragmentDoc = gql`
|
|||
swiftVersion
|
||||
macosVersion
|
||||
createdAt
|
||||
cacheableTargets
|
||||
localCacheTargetHits
|
||||
remoteCacheTargetHits
|
||||
}
|
||||
`;
|
||||
export const PendingInvitationFragmentDoc = gql`
|
||||
|
|
|
@ -11,6 +11,9 @@ export interface CommandEventDetail {
|
|||
swiftVersion: string;
|
||||
macosVersion: string;
|
||||
createdAt: Date;
|
||||
cacheableTargets: string[] | null;
|
||||
localCacheTargetHits: string[] | null;
|
||||
remoteCacheTargetHits: string[] | null;
|
||||
}
|
||||
|
||||
export const mapCommandEventDetail = ({
|
||||
|
@ -24,6 +27,9 @@ export const mapCommandEventDetail = ({
|
|||
swiftVersion,
|
||||
macosVersion,
|
||||
createdAt,
|
||||
cacheableTargets,
|
||||
localCacheTargetHits,
|
||||
remoteCacheTargetHits,
|
||||
}: CommandEventDetailFragment) => {
|
||||
return {
|
||||
id,
|
||||
|
@ -36,5 +42,8 @@ export const mapCommandEventDetail = ({
|
|||
swiftVersion,
|
||||
macosVersion,
|
||||
createdAt: new Date(createdAt),
|
||||
cacheableTargets,
|
||||
localCacheTargetHits,
|
||||
remoteCacheTargetHits,
|
||||
} as CommandEventDetail;
|
||||
};
|
||||
|
|
|
@ -11,6 +11,21 @@ module Types
|
|||
field :tuist_version, String, null: false
|
||||
field :swift_version, String, null: false
|
||||
field :macos_version, String, null: false
|
||||
field :cacheable_targets, [String], null: true
|
||||
field :local_cache_target_hits, [String], null: true
|
||||
field :remote_cache_target_hits, [String], null: true
|
||||
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||
|
||||
def cacheable_targets
|
||||
object.cacheable_targets.nil? ? nil : object.cacheable_targets.split(";")
|
||||
end
|
||||
|
||||
def local_cache_target_hits
|
||||
object.local_cache_target_hits.nil? ? nil : object.local_cache_target_hits.split(";")
|
||||
end
|
||||
|
||||
def remote_cache_target_hits
|
||||
object.remote_cache_target_hits.nil? ? nil : object.remote_cache_target_hits.split(";")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
class CommandEventCreateService < ApplicationService
|
||||
attr_reader :account_name, :project_name, :user, :name, :subcommand, :command_arguments, :duration, :client_id,
|
||||
:tuist_version, :swift_version, :macos_version
|
||||
:tuist_version, :swift_version, :macos_version, :cacheable_targets, :local_cache_target_hits,
|
||||
:remote_cache_target_hits
|
||||
|
||||
def initialize(
|
||||
project_slug:,
|
||||
|
@ -14,7 +15,10 @@ class CommandEventCreateService < ApplicationService
|
|||
client_id:,
|
||||
tuist_version:,
|
||||
swift_version:,
|
||||
macos_version:
|
||||
macos_version:,
|
||||
cacheable_targets:,
|
||||
local_cache_target_hits:,
|
||||
remote_cache_target_hits:
|
||||
)
|
||||
super()
|
||||
split_project_slug = project_slug.split("/")
|
||||
|
@ -29,6 +33,9 @@ class CommandEventCreateService < ApplicationService
|
|||
@tuist_version = tuist_version
|
||||
@swift_version = swift_version
|
||||
@macos_version = macos_version
|
||||
@cacheable_targets = cacheable_targets
|
||||
@local_cache_target_hits = local_cache_target_hits
|
||||
@remote_cache_target_hits = remote_cache_target_hits
|
||||
end
|
||||
|
||||
def call
|
||||
|
@ -43,7 +50,10 @@ class CommandEventCreateService < ApplicationService
|
|||
tuist_version: tuist_version,
|
||||
swift_version: swift_version,
|
||||
macos_version: macos_version,
|
||||
project: project
|
||||
project: project,
|
||||
cacheable_targets: cacheable_targets.nil? ? nil : cacheable_targets.join(";"),
|
||||
local_cache_target_hits: local_cache_target_hits.nil? ? nil : local_cache_target_hits.join(";"),
|
||||
remote_cache_target_hits: remote_cache_target_hits.nil? ? nil : remote_cache_target_hits.join(";")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCacheMetadataToCommandEvent < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
change_table(:command_events) do |t|
|
||||
t.string(:cacheable_targets)
|
||||
t.string(:local_cache_target_hits)
|
||||
t.string(:remote_cache_target_hits)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2022_04_10_132610) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2022_05_05_193115) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
@ -37,6 +37,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_04_10_132610) do
|
|||
t.bigint "project_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "cacheable_targets"
|
||||
t.string "local_cache_target_hits"
|
||||
t.string "remote_cache_target_hits"
|
||||
t.index ["project_id"], name: "index_command_events_on_project_id"
|
||||
end
|
||||
|
||||
|
|
|
@ -58,13 +58,16 @@ type CommandAverage {
|
|||
}
|
||||
|
||||
type CommandEvent {
|
||||
cacheableTargets: [String!]
|
||||
clientId: String!
|
||||
commandArguments: String!
|
||||
createdAt: ISO8601DateTime!
|
||||
duration: Int!
|
||||
id: ID!
|
||||
localCacheTargetHits: [String!]
|
||||
macosVersion: String!
|
||||
name: String!
|
||||
remoteCacheTargetHits: [String!]
|
||||
subcommand: String
|
||||
swiftVersion: String!
|
||||
tuistVersion: String!
|
||||
|
|
|
@ -370,6 +370,28 @@
|
|||
"name": "CommandEvent",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "cacheableTargets",
|
||||
"description": null,
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "clientId",
|
||||
"description": null,
|
||||
|
@ -460,6 +482,28 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "localCacheTargetHits",
|
||||
"description": null,
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "macosVersion",
|
||||
"description": null,
|
||||
|
@ -496,6 +540,28 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "remoteCacheTargetHits",
|
||||
"description": null,
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "subcommand",
|
||||
"description": null,
|
||||
|
|
|
@ -20,11 +20,48 @@ class CommandEventCreateServiceTest < ActiveSupport::TestCase
|
|||
client_id: "client id",
|
||||
tuist_version: "3.1.0",
|
||||
swift_version: "5.5.0",
|
||||
macos_version: "12.1.0"
|
||||
macos_version: "12.1.0",
|
||||
cacheable_targets: nil,
|
||||
local_cache_target_hits: nil,
|
||||
remote_cache_target_hits: nil
|
||||
)
|
||||
|
||||
# Then
|
||||
assert_equal project.command_events, [got]
|
||||
assert_nil got.cacheable_targets
|
||||
assert_nil got.local_cache_target_hits
|
||||
assert_nil got.remote_cache_target_hits
|
||||
assert_equal "fetch", got.name
|
||||
end
|
||||
|
||||
test "creates a cache warm command event for a given project" do
|
||||
# Given
|
||||
user = User.create!(email: "test@cloud.tuist.io", password: Devise.friendly_token.first(16))
|
||||
account = user.account
|
||||
project = Project.create!(name: "tuist-project", account_id: account.id, token: Devise.friendly_token.first(16))
|
||||
|
||||
# When
|
||||
got = CommandEventCreateService.call(
|
||||
project_slug: "#{account.name}/#{project.name}",
|
||||
user: user,
|
||||
name: "cache",
|
||||
subcommand: "warm",
|
||||
command_arguments: ["cache", "warm"],
|
||||
duration: 120,
|
||||
client_id: "client id",
|
||||
tuist_version: "3.1.0",
|
||||
swift_version: "5.5.0",
|
||||
macos_version: "12.1.0",
|
||||
cacheable_targets: ["Target1", "Target2", "Target3", "Target4"],
|
||||
local_cache_target_hits: ["Target1"],
|
||||
remote_cache_target_hits: ["Target2", "Target4"]
|
||||
)
|
||||
|
||||
# Then
|
||||
assert_equal project.command_events, [got]
|
||||
assert_equal "cache", got.name
|
||||
assert_equal "Target1;Target2;Target3;Target4", got.cacheable_targets
|
||||
assert_equal "Target1", got.local_cache_target_hits
|
||||
assert_equal "Target2;Target4", got.remote_cache_target_hits
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue