Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for auto generating metrics #1283

Merged
merged 50 commits into from Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
8b2b643
POC for auto-generating metrics from Segment/BigQuery
mknowlton89 May 12, 2023
e24e9f0
Merge branch 'main' into mk/auto-metrics
mknowlton89 May 16, 2023
49734e1
Adds some logic to build the metric sql query and only builds those t…
mknowlton89 May 16, 2023
81cab6f
Adds logic for schemaFormat to customize timestamp column, and adds v…
mknowlton89 May 16, 2023
7538361
Adds basic support for count and revenue metric types. Still not sure…
mknowlton89 May 16, 2023
71356da
Merge branch 'main' into mk/auto-metrics
mknowlton89 May 17, 2023
6d4be65
Adds some flexibility to the SQl queries to handle different table na…
mknowlton89 May 17, 2023
75aba06
Overall cleanup and renaming of methods.
mknowlton89 May 17, 2023
3503ef4
Removing unused import.
mknowlton89 May 18, 2023
4935fa3
Updates the front end to consume the new supportsAutoGeneratedMetrics…
mknowlton89 May 18, 2023
eff2b94
Expanding support for all data warehouses for Segment & Rudderstack -…
mknowlton89 May 18, 2023
7753358
Updates the mssql integration to include an optional default schema i…
mknowlton89 May 18, 2023
5eeed8f
Refactors the code a bit to use the same method to get the from claus…
mknowlton89 May 19, 2023
6ccfdfe
Improves error handling
mknowlton89 May 19, 2023
d10ef04
Resets controller logic back after testing, and renames helper method.
mknowlton89 May 19, 2023
145b9cd
Refactored a bit based on Jeremy's feedback, needs a lot of cleanup, …
mknowlton89 May 24, 2023
e2dd627
Merge branch 'main' into mk/auto-metrics
mknowlton89 May 24, 2023
08c37e1
Starts scaffolding out how we can build additional metrics when retre…
mknowlton89 May 24, 2023
63d9788
Adds better typing and refactors logic to simplify some things, while…
mknowlton89 May 24, 2023
5a161ec
Includes a big refactor that simplifies things a lot - we're now gett…
mknowlton89 May 26, 2023
c99eccc
Improves the front end experience.
mknowlton89 May 26, 2023
33aa2a5
Introduces a method to build a count metric name that is pluralized, …
mknowlton89 May 27, 2023
7d275d5
Improved the UI a bit to include the SQL Preview functionality, and b…
mknowlton89 May 28, 2023
652eb75
Switched the front end experience over to what Luke suggested - left …
mknowlton89 Jun 1, 2023
10367b3
Removed commented out code.
mknowlton89 Jun 1, 2023
9785c2d
Fixed a type issue
mknowlton89 Jun 1, 2023
5f90e09
First round of self PR review adjustments.
mknowlton89 Jun 1, 2023
9687da0
Adds back in an optional type within the Integration.ts type file, an…
mknowlton89 Jun 1, 2023
b734281
Removes console log
mknowlton89 Jun 1, 2023
05be520
Merge branch 'main' into mk/auto-metrics
mknowlton89 Jun 1, 2023
351cb7d
Adds support for screens tables for Segment & Rudderstack
mknowlton89 Jun 2, 2023
1e565e9
Cleans up the logic for the various data warehouses supported by Segm…
mknowlton89 Jun 2, 2023
a1393a8
Merge branch 'main' into mk/auto-metrics
mknowlton89 Jun 2, 2023
aea2c57
Adds types for includesScreensTable and includesPagesTable that were …
mknowlton89 Jun 2, 2023
9c72050
Resolving conflicts
mknowlton89 Jun 5, 2023
09d4262
Addresses almost all of Jeremy's feedback - updates the data structur…
mknowlton89 Jun 6, 2023
faf99cb
Removes console log
mknowlton89 Jun 6, 2023
081d2ce
Fixed a few failing types
mknowlton89 Jun 6, 2023
dfca94c
Refactored how we were building the FROM clause within SqlIntegration.
mknowlton89 Jun 6, 2023
700f541
Added some styling to the Sql preview row so it matches how we show e…
mknowlton89 Jun 6, 2023
ab0bd25
Merge branch 'main' into mk/auto-metrics
mknowlton89 Jun 6, 2023
cc1a23a
General PR cleanup, improved mobile styling of table.
mknowlton89 Jun 7, 2023
4b0aca2
Refactors how we build the tablePath based on Jeremy's feedback
mknowlton89 Jun 7, 2023
55a88c9
Merge branch 'main' into mk/auto-metrics
mknowlton89 Jun 8, 2023
e37b249
Fixes from code review
jdorn Jun 8, 2023
3eb2a8f
Merge branch 'main' into mk/auto-metrics
mknowlton89 Jun 8, 2023
ff9f3ea
Removed an incorrect property on the Postgres integration class, and …
mknowlton89 Jun 8, 2023
a22af49
refactors the AutoMetricCard to look for specific metric types to ens…
mknowlton89 Jun 8, 2023
0973a4f
Merge branch 'main' into mk/auto-metrics
mknowlton89 Jun 9, 2023
dfbe91c
Merge branch 'main' into mk/auto-metrics
mknowlton89 Jun 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/back-end/src/app.ts
Expand Up @@ -483,6 +483,10 @@ app.get("/datasource/:id", datasourcesController.getDataSource);
app.post("/datasources", datasourcesController.postDataSources);
app.put("/datasource/:id", datasourcesController.putDataSource);
app.delete("/datasource/:id", datasourcesController.deleteDataSource);
app.post(
"/datasource/:id/auto-metrics",
datasourcesController.postAutoGeneratedMetricsToCreate
);

// Information Schemas
app.get(
Expand Down
77 changes: 76 additions & 1 deletion packages/back-end/src/controllers/datasources.ts
Expand Up @@ -42,6 +42,9 @@ import {
import { EventAuditUserForResponseLocals } from "../events/event-types";
import { deleteInformationSchemaById } from "../models/InformationSchemaModel";
import { deleteInformationSchemaTablesByInformationSchemaId } from "../models/InformationSchemaTablesModel";
import { queueCreateAutoGeneratedMetrics } from "../jobs/createAutoGeneratedMetrics";
import { TrackedEventData } from "../types/Integration";
import { MetricType } from "../../types/metric";

export async function postSampleData(
req: AuthRequest,
Expand Down Expand Up @@ -420,14 +423,23 @@ export async function putDataSource(
params: DataSourceParams;
settings: DataSourceSettings;
projects?: string[];
metricsToCreate?: { name: string; type: MetricType; sql: string }[];
},
{ id: string }
>,
res: Response
) {
const { org } = getOrgFromReq(req);
const { id } = req.params;
const { name, description, type, params, settings, projects } = req.body;
const {
name,
description,
type,
params,
settings,
projects,
metricsToCreate,
} = req.body;

const datasource = await getDataSourceById(id, org.id);
if (!datasource) {
Expand Down Expand Up @@ -455,6 +467,14 @@ export async function putDataSource(
return;
}

if (metricsToCreate?.length) {
await queueCreateAutoGeneratedMetrics(
datasource.id,
org.id,
metricsToCreate
);
}

try {
const updates: Partial<DataSourceInterface> = {
dateUpdated: new Date(),
Expand Down Expand Up @@ -602,3 +622,58 @@ export async function testLimitedQuery(
error,
});
}

export async function postAutoGeneratedMetricsToCreate(
req: AuthRequest<null, { id: string }>,
res: Response
) {
const { org } = getOrgFromReq(req);
const { id } = req.params;

const dataSourceObj = await getDataSourceById(id, org.id);
if (!dataSourceObj) {
res.status(404).json({
status: 404,
message: "Invalid data source: " + id,
});
return;
}

req.checkPermissions(
"createMetrics",
dataSourceObj.projects?.length ? dataSourceObj.projects : ""
);

const integration = getSourceIntegrationObject(dataSourceObj);

try {
if (
!integration.getEventsTrackedByDatasource ||
!integration.settings.schemaFormat ||
!integration.getSourceProperties().supportsAutoGeneratedMetrics
) {
throw new Error("Datasource does not support auto-metrics");
}

const trackedEvents: TrackedEventData[] = await integration.getEventsTrackedByDatasource(
integration.settings.schemaFormat
);

if (!trackedEvents.length) {
throw new Error("No recent events found");
}

return res.status(200).json({
status: 200,
trackedEvents,
});
} catch (e) {
res.status(200).json({
status: 200,
trackedEvents: [],
message:
"We were unable to identify any metrics to generate for you automatically. You can still create metrics manually.",
});
return;
}
}
2 changes: 2 additions & 0 deletions packages/back-end/src/init/queue.ts
Expand Up @@ -6,6 +6,7 @@ import addMetricUpdateJob from "../jobs/updateMetrics";
import addProxyUpdateJob from "../jobs/proxyUpdate";
import createInformationSchemaJob from "../jobs/createInformationSchema";
import updateInformationSchemaJob from "../jobs/updateInformationSchema";
import createAutoGeneratedMetrics from "../jobs/createAutoGeneratedMetrics";
import { CRON_ENABLED } from "../util/secrets";
import { getAgendaInstance } from "../services/queueing";
import updateStaleInformationSchemaTable from "../jobs/updateStaleInformationSchemaTable";
Expand All @@ -23,6 +24,7 @@ export async function queueInit() {
addProxyUpdateJob(agenda);
createInformationSchemaJob(agenda);
updateInformationSchemaJob(agenda);
createAutoGeneratedMetrics(agenda);
updateStaleInformationSchemaTable(agenda);

await agenda.start();
Expand Down
13 changes: 3 additions & 10 deletions packages/back-end/src/integrations/Athena.ts
Expand Up @@ -2,13 +2,13 @@ import { decryptDataSourceParams } from "../services/datasource";
import { runAthenaQuery } from "../services/athena";
import { AthenaConnectionParams } from "../../types/integrations/athena";
import { FormatDialect } from "../util/sql";
import { MissingDatasourceParamsError } from "../types/Integration";
import SqlIntegration from "./SqlIntegration";

export default class Athena extends SqlIntegration {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
params: AthenaConnectionParams;
requiresSchema = false;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<AthenaConnectionParams>(
encryptedParams
Expand Down Expand Up @@ -49,14 +49,7 @@ export default class Athena extends SqlIntegration {
ensureFloat(col: string): string {
return `1.0*${col}`;
}
getInformationSchemaFromClause(): string {
if (!this.params.catalog)
throw new MissingDatasourceParamsError(
"To view the information schema for an Athena data source, you must define a default catalog. Please add a default catalog by editing the datasource's connection settings."
);
return `${this.params.catalog}.information_schema.columns`;
}
getInformationSchemaTableFromClause(databaseName: string): string {
return `${databaseName}.information_schema.columns`;
getDefaultDatabase() {
return this.params.catalog || "";
}
}
30 changes: 13 additions & 17 deletions packages/back-end/src/integrations/BigQuery.ts
Expand Up @@ -4,13 +4,13 @@ import { decryptDataSourceParams } from "../services/datasource";
import { BigQueryConnectionParams } from "../../types/integrations/bigquery";
import { IS_CLOUD } from "../util/secrets";
import { FormatDialect } from "../util/sql";
import { MissingDatasourceParamsError } from "../types/Integration";
import SqlIntegration from "./SqlIntegration";

export default class BigQuery extends SqlIntegration {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
params: BigQueryConnectionParams;
requiresEscapingPath = true;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<BigQueryConnectionParams>(
encryptedParams
Expand Down Expand Up @@ -82,21 +82,17 @@ export default class BigQuery extends SqlIntegration {
castUserDateCol(column: string): string {
return `CAST(${column} as DATETIME)`;
}
getInformationSchemaFromClause(): string {
if (!this.params.projectId)
throw new Error(
"No projectId provided. In order to get the information schema, you must provide a projectId."
);
if (!this.params.defaultDataset)
throw new MissingDatasourceParamsError(
"To view the information schema for a BigQuery dataset, you must define a default dataset. Please add a default dataset by editing the datasource's connection settings."
);
return `\`${this.params.projectId}.${this.params.defaultDataset}.INFORMATION_SCHEMA.COLUMNS\``;
}
getInformationSchemaTableFromClause(
databaseName: string,
tableSchema: string
): string {
return `\`${databaseName}.${tableSchema}.INFORMATION_SCHEMA.COLUMNS\``;
getDefaultDatabase() {
return this.params.projectId || "";
}
getDefaultSchema() {
return this.params.defaultDataset;
}
getInformationSchemaTable(schema?: string, database?: string): string {
return this.generateTablePath(
"INFORMATION_SCHEMA.COLUMNS",
schema,
database
);
}
}
2 changes: 2 additions & 0 deletions packages/back-end/src/integrations/ClickHouse.ts
Expand Up @@ -7,6 +7,8 @@ export default class ClickHouse extends SqlIntegration {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
params: ClickHouseConnectionParams;
requiresDatabase = false;
requiresSchema = false;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<ClickHouseConnectionParams>(
encryptedParams
Expand Down
2 changes: 2 additions & 0 deletions packages/back-end/src/integrations/Databricks.ts
Expand Up @@ -6,6 +6,8 @@ import SqlIntegration from "./SqlIntegration";

export default class Databricks extends SqlIntegration {
params!: DatabricksConnectionParams;
requiresDatabase = false;
requiresSchema = false;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<DatabricksConnectionParams>(
encryptedParams
Expand Down
13 changes: 3 additions & 10 deletions packages/back-end/src/integrations/Mssql.ts
@@ -1,14 +1,14 @@
import { MssqlConnectionParams } from "../../types/integrations/mssql";
import { decryptDataSourceParams } from "../services/datasource";
import { FormatDialect } from "../util/sql";
import { MissingDatasourceParamsError } from "../types/Integration";
import { findOrCreateConnection } from "../util/mssqlPoolManager";
import SqlIntegration from "./SqlIntegration";

export default class Mssql extends SqlIntegration {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
params: MssqlConnectionParams;
requiresSchema = false;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<MssqlConnectionParams>(
encryptedParams
Expand Down Expand Up @@ -67,14 +67,7 @@ export default class Mssql extends SqlIntegration {
formatDateTimeString(col: string): string {
return `CONVERT(VARCHAR(25), ${col}, 121)`;
}
getInformationSchemaFromClause(): string {
if (!this.params.database)
throw new MissingDatasourceParamsError(
"To view the information schema for a MS Sql dataset, you must define a default database. Please add a default database by editing the datasource's connection settings."
);
return `${this.params.database}.information_schema.columns`;
}
getInformationSchemaTableFromClause(databaseName: string): string {
return `${databaseName}.information_schema.columns`;
getDefaultDatabase() {
return this.params.database;
}
}
2 changes: 2 additions & 0 deletions packages/back-end/src/integrations/Mysql.ts
Expand Up @@ -9,6 +9,8 @@ export default class Mysql extends SqlIntegration {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
params: MysqlConnectionParams;
requiresDatabase = false;
requiresSchema = false;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<MysqlConnectionParams>(
encryptedParams
Expand Down
2 changes: 2 additions & 0 deletions packages/back-end/src/integrations/Postgres.ts
Expand Up @@ -6,6 +6,8 @@ import SqlIntegration from "./SqlIntegration";

export default class Postgres extends SqlIntegration {
params!: PostgresConnectionParams;
requiresDatabase = false;
requiresSchema = false;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<PostgresConnectionParams>(
encryptedParams
Expand Down
13 changes: 3 additions & 10 deletions packages/back-end/src/integrations/Presto.ts
Expand Up @@ -3,7 +3,6 @@ import { Client, IPrestoClientOptions } from "presto-client";
import { decryptDataSourceParams } from "../services/datasource";
import { PrestoConnectionParams } from "../../types/integrations/presto";
import { FormatDialect } from "../util/sql";
import { MissingDatasourceParamsError } from "../types/Integration";
import SqlIntegration from "./SqlIntegration";

// eslint-disable-next-line
Expand All @@ -13,6 +12,7 @@ export default class Presto extends SqlIntegration {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
params: PrestoConnectionParams;
requiresSchema = false;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<PrestoConnectionParams>(
encryptedParams
Expand Down Expand Up @@ -106,14 +106,7 @@ export default class Presto extends SqlIntegration {
ensureFloat(col: string): string {
return `CAST(${col} AS DOUBLE)`;
}
getInformationSchemaFromClause(): string {
if (!this.params.catalog)
throw new MissingDatasourceParamsError(
"To view the information schema for a Presto data source, you must define a default catalog. Please add a default catalog by editing the datasource's connection settings."
);
return `${this.params.catalog}.information_schema.columns`;
}
getInformationSchemaTableFromClause(databaseName: string): string {
return `${databaseName}.information_schema.columns`;
getDefaultDatabase() {
return this.params.catalog || "";
}
}
5 changes: 1 addition & 4 deletions packages/back-end/src/integrations/Redshift.ts
Expand Up @@ -34,10 +34,7 @@ export default class Redshift extends SqlIntegration {
ensureFloat(col: string): string {
return `${col}::float`;
}
getInformationSchemaFromClause(): string {
return "SVV_COLUMNS";
}
getInformationSchemaTableFromClause(): string {
getInformationSchemaTable(): string {
return "SVV_COLUMNS";
}
}
12 changes: 3 additions & 9 deletions packages/back-end/src/integrations/Snowflake.ts
Expand Up @@ -8,6 +8,7 @@ export default class Snowflake extends SqlIntegration {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
params: SnowflakeConnectionParams;
requiresSchema = false;
setParams(encryptedParams: string) {
this.params = decryptDataSourceParams<SnowflakeConnectionParams>(
encryptedParams
Expand All @@ -34,17 +35,10 @@ export default class Snowflake extends SqlIntegration {
ensureFloat(col: string): string {
return `CAST(${col} AS DOUBLE)`;
}
getInformationSchemaFromClause(): string {
if (!this.params.database)
throw new Error(
"No database provided. In order to get the information schema, you must provide a database."
);
return `${this.params.database}.information_schema.columns`;
}
getInformationSchemaWhereClause(): string {
return "table_schema NOT IN ('INFORMATION_SCHEMA')";
}
getInformationSchemaTableFromClause(databaseName: string): string {
return `${databaseName}.information_schema.columns`;
getDefaultDatabase() {
return this.params.database || "";
}
}