Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
4e24685
add todo
ken-zlai Jan 22, 2025
7e42a2f
add JoinRequest
ken-zlai Jan 23, 2025
649e69c
implement a new json serializer which handles unions properly
ken-zlai Jan 23, 2025
d3c10c7
create ConfHandler, implement getJoin
ken-zlai Jan 23, 2025
3c86580
add getJoin to frontend
ken-zlai Jan 23, 2025
4b59d12
add /thrift route for testing purposes
ken-zlai Jan 23, 2025
4b37045
remove todo
ken-zlai Jan 23, 2025
c7658d1
add new Join() explanation
ken-zlai Jan 23, 2025
ec318f1
add function explanation comment
ken-zlai Jan 23, 2025
c424a54
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 23, 2025
f7b52f3
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 23, 2025
cefde52
add conf thrift
ken-zlai Jan 23, 2025
c08151a
implement getConf (not fully tested)
ken-zlai Jan 23, 2025
124e9ff
frontend api calls for model, groupby, staging query
ken-zlai Jan 23, 2025
c5d111e
use LogicalNode type
ken-zlai Jan 23, 2025
7b534cc
implement /api/v1/conf/list
ken-zlai Jan 23, 2025
e581d3e
frontend api stuff
ken-zlai Jan 23, 2025
9425487
similar spacing
ken-zlai Jan 23, 2025
a3cf764
get a groupby and model that exist
ken-zlai Jan 24, 2025
7c981b7
use ConfType
ken-zlai Jan 24, 2025
3ca3a09
change routes
ken-zlai Jan 24, 2025
7fd4fbb
create LogicalNodeTable
ken-zlai Jan 24, 2025
c29fedd
show joins/models/staging queries/groupbys on their page
ken-zlai Jan 24, 2025
bdd3858
just use table directly, its simpler
ken-zlai Jan 24, 2025
09b0dbc
empty state for table
ken-zlai Jan 24, 2025
4686e94
put back logic to only make risk.user_transactions.txn_join clickable
ken-zlai Jan 24, 2025
dc1a110
fix npm run check
ken-zlai Jan 24, 2025
cf5c1ad
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 27, 2025
3627e81
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 27, 2025
d183759
create JoinDriftRequest and JoinDriftResponse
ken-zlai Jan 28, 2025
cf440e2
implement drift handler
ken-zlai Jan 28, 2025
8868ed8
getJoinDrift on the frontend
ken-zlai Jan 28, 2025
9d90866
add featureName to JoinDriftRequest
ken-zlai Jan 29, 2025
e2bede7
frontend endpoint for featureDrift
ken-zlai Jan 29, 2025
f06f461
add hub endpoint: /api/v1/join/:name/feature/:featureName/drift
ken-zlai Jan 29, 2025
df5ea52
implement getFeatureDrift and filtering
ken-zlai Jan 29, 2025
1201606
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 29, 2025
3db534f
simplify some duplicate code
ken-zlai Jan 29, 2025
1f3afb3
scalafmt
ken-zlai Jan 29, 2025
78913d9
modify search endpoint to return ConfListResponse, also search thru j…
ken-zlai Jan 29, 2025
69008fc
frontend changes for new search
ken-zlai Jan 29, 2025
ca7047e
comments :)
ken-zlai Jan 29, 2025
4b176d1
put back old error code
ken-zlai Jan 29, 2025
05a5520
confhandler tests
ken-zlai Jan 29, 2025
b837620
delete old code
ken-zlai Jan 29, 2025
3e05c32
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 29, 2025
eef46a1
sbt scalafixAll, add logs
ken-zlai Jan 29, 2025
bf58d8c
use new serializer each time
ken-zlai Jan 30, 2025
224a894
add magicNullLong
ken-zlai Jan 30, 2025
a6b7b56
add JoinSummaryRequest
ken-zlai Jan 30, 2025
7d55036
create summary endpoint
ken-zlai Jan 30, 2025
b723b59
implement getFeatureSummary
ken-zlai Jan 30, 2025
b7edf22
call api from frontend
ken-zlai Jan 30, 2025
6ac85b3
fmt and fix
ken-zlai Jan 30, 2025
e8a7686
change magic values to safe value
ken-zlai Jan 30, 2025
8d59fec
oops put search endpoint back
ken-zlai Jan 31, 2025
70f1614
Drift handler test
ken-zlai Jan 31, 2025
21b1ef5
column -> feature
ken-zlai Jan 31, 2025
8cadcd4
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 31, 2025
bbeb642
logger.info -> logger.debug
ken-zlai Jan 31, 2025
e352adc
fix and fmt
ken-zlai Jan 31, 2025
d288225
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 31, 2025
46efc2f
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 31, 2025
d01fd09
Merge branch 'main' into ken/thrift-observability-apis
ken-zlai Jan 31, 2025
431fc7b
remove getModels
ken-zlai Jan 31, 2025
f6d848e
more resiliant code for TimeSeriesHandler
ken-zlai Jan 31, 2025
eb232f7
remove old import
ken-zlai Jan 31, 2025
6aca8a5
delete unused getJoins
ken-zlai Jan 31, 2025
7b21828
cleaner code for routing stuff
ken-zlai Jan 31, 2025
a18fb90
remove unused
ken-zlai Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions api/src/main/scala/ai/chronon/api/Constants.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,7 @@ object Constants {
val extensionsToIgnore: Array[String] = Array(".class", ".csv", ".java", ".scala", ".py", ".DS_Store")
val foldersToIgnore: Array[String] = Array(".git")

// import base64
// text_bytes = "chronon".encode('utf-8')
// base64_str = base64.b64encode(text_bytes)
// int.from_bytes(base64.b64decode(base64_str), "big")
//
// output: 27980863399423854

val magicNullDouble: java.lang.Double = -27980863399423854.0
// A negative integer within the safe range for both long and double in JavaScript, Java, Scala, Python
val magicNullLong: java.lang.Long = -1234567890L
val magicNullDouble: java.lang.Double = -1234567890.0
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import ai.chronon.api.Constants
import ai.chronon.api.ScalaJavaConversions.JListOps
import ai.chronon.api.ThriftJsonCodec
import ai.chronon.observability.TileDriftSeries
import ai.chronon.observability.TileSummarySeries
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import java.lang.{Double => JDouble}
import java.lang.{Long => JLong}
import scala.jdk.CollectionConverters.asScalaBufferConverter

class TileSeriesSerializationTest extends AnyFlatSpec with Matchers {

Expand All @@ -27,8 +30,49 @@ class TileSeriesSerializationTest extends AnyFlatSpec with Matchers {

val jsonStr = ThriftJsonCodec.toJsonStr(tileDriftSeries)

jsonStr should be ("""{"percentileDriftSeries":[0.1,-2.7980863399423856E16,-2.7980863399423856E16,-2.7980863399423856E16,0.5]}""")
jsonStr should be (s"""{"percentileDriftSeries":[0.1,${Constants.magicNullDouble},${Constants.magicNullDouble},${Constants.magicNullDouble},0.5]}""")
}

it should "deserialize double values correctly" in {
val json = s"""{"percentileDriftSeries":[0.1,${Constants.magicNullDouble},${Constants.magicNullDouble},${Constants.magicNullDouble},0.5]}"""

val series = ThriftJsonCodec.fromJsonStr[TileDriftSeries](json, true, classOf[TileDriftSeries])(manifest[TileDriftSeries])

val drifts = series.getPercentileDriftSeries.asScala.toList
drifts.size should be (5)
drifts(0) should be (0.1)
drifts(1) should be (Constants.magicNullDouble)
drifts(2) should be (Constants.magicNullDouble)
drifts(3) should be (Constants.magicNullDouble)
drifts(4) should be (0.5)
}

"TileSummarySeries" should "serialize with nulls and special long values" in {
val tileSummarySeries = new TileSummarySeries()

val counts: Seq[JLong] = Seq(100L, null, Long.MaxValue, Constants.magicNullLong, 500L)
.map(v => if (v == null) Constants.magicNullLong else v.asInstanceOf[JLong])

val countsList: java.util.List[JLong] = counts.toJava
tileSummarySeries.setCount(countsList)

val jsonStr = ThriftJsonCodec.toJsonStr(tileSummarySeries)

jsonStr should be (s"""{"count":[100,${Constants.magicNullLong},9223372036854775807,${Constants.magicNullLong},500]}""")
}

it should "deserialize long values correctly" in {
val json = s"""{"count":[100,${Constants.magicNullLong},9223372036854775807,${Constants.magicNullLong},500]}"""

val series = ThriftJsonCodec.fromJsonStr[TileSummarySeries](json, true, classOf[TileSummarySeries])(manifest[TileSummarySeries])

val counts = series.getCount.asScala.toList
counts.size should be (5)
counts(0) should be (100L)
counts(1) should be (Constants.magicNullLong)
counts(2) should be (Long.MaxValue)
counts(3) should be (Constants.magicNullLong)
counts(4) should be (500L)
}

}
2 changes: 1 addition & 1 deletion api/thrift/api.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -432,4 +432,4 @@ struct Model {
3: optional TDataType outputSchema
4: optional Source source
5: optional map<string, string> modelParams
}
}
36 changes: 36 additions & 0 deletions api/thrift/hub.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,39 @@ struct Submission {
20: optional i64 finishedTs
21: optional DateRange dateRange
}

enum ConfType{
STAGING_QUERY = 1
GROUP_BY = 2
JOIN = 3
MODEL = 4
}

struct ConfRequest {
1: optional string confName
2: optional ConfType confType

// one of either branch or version are set - otherwise we will pull conf for main branch
3: optional string branch
4: optional string version
}

/**
* lists all confs of the specified type
*/
struct ConfListRequest {
1: optional ConfType confType

// if not specified we will pull conf list for main branch
2: optional string branch
}

/**
* Response for listing configurations of a specific type
*/
struct ConfListResponse {
1: optional list<api.Join> joins
2: optional list<api.GroupBy> groupBys
3: optional list<api.Model> models
4: optional list<api.StagingQuery> stagingQueries
}
22 changes: 21 additions & 1 deletion api/thrift/observability.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,24 @@ struct DriftSpec {

// default drift metric to use
6: optional DriftMetric driftMetric = DriftMetric.JENSEN_SHANNON
}
}

struct JoinDriftRequest {
1: required string name
2: required i64 startTs
3: required i64 endTs
6: optional string offset // Format: "24h" or "7d"
7: optional DriftMetric algorithm
8: optional string columnName
}

struct JoinDriftResponse {
1: required list<TileDriftSeries> driftSeries
}

struct JoinSummaryRequest {
1: required string name
2: required i64 startTs
3: required i64 endTs
8: required string columnName
}
8 changes: 4 additions & 4 deletions frontend/src/lib/api/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ describe('API module', () => {
text: () => Promise.resolve(JSON.stringify(mockResponse))
});

const result = await api.getModels();
const result = await api.getModelList();

expect(mockFetch).toHaveBeenCalledWith(`/api/v1/models`, {
expect(mockFetch).toHaveBeenCalledWith(`/api/v1/conf/list?confType=MODEL`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
Expand All @@ -47,7 +47,7 @@ describe('API module', () => {
text: () => Promise.resolve('')
});

const result = await api.getModels();
const result = await api.getModelList();

expect(result).toEqual({});
});
Expand All @@ -58,7 +58,7 @@ describe('API module', () => {
status: 404
});

await api.getModels();
await api.getModelList();

expect(error).toHaveBeenCalledWith(404);
});
Expand Down
121 changes: 103 additions & 18 deletions frontend/src/lib/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { error } from '@sveltejs/kit';
import type { FeatureResponse, JoinTimeSeriesResponse } from '$lib/types/Model/Model';
import type {
FeatureResponse,
JoinsResponse,
JoinTimeSeriesResponse,
ModelsResponse
} from '$lib/types/Model/Model';
Join,
GroupBy,
Model,
StagingQuery,
IJoinDriftRequestArgs,
IJoinDriftResponseArgs,
ITileSummarySeries,
IJoinSummaryRequestArgs
} from '$lib/types/codegen';
import { ConfType, DriftMetric } from '$lib/types/codegen';
import type { ConfListResponse } from '$lib/types/codegen/ConfListResponse';

export type ApiOptions = {
base?: string;
Expand All @@ -30,25 +37,35 @@ export class Api {
this.#accessToken = opts.accessToken;
}

// TODO: eventually move this to a model-specific file/decide on a good project structure for organizing api calls
async getModels() {
return this.#send<ModelsResponse>('models');
}

async getJoins(offset: number = 0, limit: number = 10) {
async getConf(name: string, type: ConfType) {
const params = new URLSearchParams({
offset: offset.toString(),
limit: limit.toString()
confName: name,
confType: ConfType[type]
});
return this.#send<JoinsResponse>(`joins?${params.toString()}`);
return this.#send<Join | GroupBy | Model>(`conf?${params.toString()}`);
}

async getJoin(name: string): Promise<Join> {
return this.getConf(name, ConfType.JOIN) as Promise<Join>;
}

async search(term: string, limit: number = 20) {
async getGroupBy(name: string): Promise<GroupBy> {
return this.getConf(name, ConfType.GROUP_BY) as Promise<GroupBy>;
}

async getModel(name: string): Promise<Model> {
return this.getConf(name, ConfType.MODEL) as Promise<Model>;
}

async getStagingQuery(name: string): Promise<StagingQuery> {
return this.getConf(name, ConfType.STAGING_QUERY) as Promise<StagingQuery>;
}

async search(term: string) {
const params = new URLSearchParams({
term,
limit: limit.toString()
confName: term
});
return this.#send<ModelsResponse>(`search?${params.toString()}`);
return this.#send<ConfListResponse>(`search?${params.toString()}`);
}

async getJoinTimeseries({
Expand Down Expand Up @@ -115,6 +132,74 @@ export class Api {
);
}

async getConfList(type: ConfType): Promise<ConfListResponse> {
const params = new URLSearchParams({
confType: ConfType[type]
});
return this.#send<ConfListResponse>(`conf/list?${params.toString()}`);
}

async getJoinList(): Promise<ConfListResponse> {
return this.getConfList(ConfType.JOIN);
}

async getGroupByList(): Promise<ConfListResponse> {
return this.getConfList(ConfType.GROUP_BY);
}

async getModelList(): Promise<ConfListResponse> {
return this.getConfList(ConfType.MODEL);
}

async getStagingQueryList(): Promise<ConfListResponse> {
return this.getConfList(ConfType.STAGING_QUERY);
}

async getJoinDrift({
name,
startTs,
endTs,
offset = '10h',
algorithm = DriftMetric.PSI
}: IJoinDriftRequestArgs) {
const params = new URLSearchParams({
startTs: startTs.toString(),
endTs: endTs.toString(),
offset,
algorithm: DriftMetric[algorithm]
});
return this.#send<IJoinDriftResponseArgs>(`join/${name}/drift?${params.toString()}`);
}

async getColumnDrift({
name,
columnName,
startTs,
endTs,
offset = '10h',
algorithm = DriftMetric.PSI
}: IJoinDriftRequestArgs) {
const params = new URLSearchParams({
startTs: startTs.toString(),
endTs: endTs.toString(),
offset,
algorithm: DriftMetric[algorithm]
});
return this.#send<IJoinDriftResponseArgs>(
`join/${name}/column/${columnName}/drift?${params.toString()}`
);
}

async getColumnSummary({ name, columnName, startTs, endTs }: IJoinSummaryRequestArgs) {
const params = new URLSearchParams({
startTs: startTs.toString(),
endTs: endTs.toString()
});
return this.#send<ITileSummarySeries>(
`join/${name}/column/${columnName}/summary?${params.toString()}`
);
}

async #send<Data = unknown>(resource: string, options?: ApiRequestOptions) {
let url = `${this.#base}/${resource}`;

Expand Down
64 changes: 64 additions & 0 deletions frontend/src/lib/components/LogicalNodeTable.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script lang="ts">
import type { IJoin, IGroupBy, IModel, IStagingQuery } from '$lib/types/codegen';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/ui/table';
import Separator from '$lib/components/ui/separator/separator.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
import ActionButtons from '$lib/components/ActionButtons.svelte';
const {
title,
items,
basePath
}: { title: string; items: (IJoin | IGroupBy | IModel | IStagingQuery)[]; basePath: string } =
$props();
</script>

<PageHeader {title} />

<div class="w-full">
<ActionButtons class="mb-4" />
</div>

<Separator fullWidthExtend={true} />

<Table>
<TableHeader>
<TableRow>
<TableHead>{title}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if items.length === 0}
<TableRow>
<TableCell>
No {title.toLowerCase()} found.
</TableCell>
</TableRow>
{:else}
{#each items as item}
<TableRow>
<TableCell>
<!-- todo: enable all items once we have data for them -->
<a
href={`${basePath}/${encodeURIComponent(item.metaData?.name ?? '')}`}
class="hover:underline {item.metaData?.name !== 'risk.user_transactions.txn_join'
? 'pointer-events-none opacity-50'
: ''}"
>
{item.metaData?.name}
</a>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>

<Separator fullWidthExtend={true} />
Loading