Skip to content

Commit 3258aa1

Browse files
Add integration test that ingests Seafowl GitHub repo with airbyte-github
If environment variable `VITE_TEST_GITHUB_PAT_SECRET` is defined, then run an integration test which ingests data from the Seafowl GitHub repository into a Splitgraph repo `madatdata-test-github-ingestion` under the namespace of the username associated with the integration test `API_KEY` and `API_SECRET`. Technically, it's hardcoded to be disabled right now so hot reload testing doesn't spam ingestion, but it works, e.g. see this repository which was ingested with the new code in `db-splitgraph.test.ts`: https://www.splitgraph.com/miles/madatdata-test-github-ingestion/latest/-/tables
1 parent 78af9cd commit 3258aa1

7 files changed

Lines changed: 185 additions & 79 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ VITE_TEST_INTEGRATION=1
4242
VITE_TEST_DDN_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
4343
VITE_TEST_DDN_API_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
4444
VITE_TEST_SEAFOWL_SECRET=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
45+
VITE_TEST_GITHUB_PAT=uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
4546
```
4647

4748
Then simply append `--mode integration` flag to any variant of `yarn test` that

packages/db-splitgraph/db-splitgraph.test.ts

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { makeDb } from "./db-splitgraph";
55
import { SplitgraphImportCSVPlugin } from "./plugins/importers/splitgraph-import-csv-plugin";
66
import { ExportQueryPlugin } from "./plugins/exporters/export-query-plugin";
77

8-
import { shouldSkipIntegrationTests } from "@madatdata/test-helpers/env-config";
8+
import {
9+
shouldSkipIntegrationTests,
10+
shouldSkipIntegrationTestsForGitHubExternalDataSource,
11+
} from "@madatdata/test-helpers/env-config";
912
import { setupMswServerTestHooks } from "@madatdata/test-helpers/msw-server-hooks";
1013
import { setupMemo } from "@madatdata/test-helpers/setup-memo";
1114
import { compose, graphql, rest, type DefaultBodyType } from "msw";
@@ -113,26 +116,69 @@ const createRealDb = () => {
113116
graphqlEndpoint: defaultHost.baseUrls.gql,
114117
}),
115118

119+
new AirbyteGithubImportPlugin({
120+
graphqlEndpoint: defaultHost.baseUrls.gql,
121+
}),
122+
116123
new ExportQueryPlugin({
117124
graphqlEndpoint: defaultHost.baseUrls.gql,
118125
}),
119126
],
120127
});
121128
};
122129

123-
// describe("importData for AirbyeGitHubImportPlugin", () => {
124-
// it("can use the plugin", async () => {
125-
// const db = createDb();
130+
// @ts-expect-error https://stackoverflow.com/a/70711231
131+
const GITHUB_PAT_SECRET = import.meta.env.VITE_TEST_GITHUB_PAT_SECRET;
126132

127-
// // db.importData("airbyte-github", {params: {repository: "madatdata/test-repo"}})
133+
describe.skipIf(shouldSkipIntegrationTestsForGitHubExternalDataSource())(
134+
"importData for AirbyeGitHubImportPlugin",
135+
() => {
136+
it("can use the plugin", async () => {
137+
const db = createRealDb();
128138

129-
// // await db.importData("airbyte-github", { params: }
139+
const { username: namespace } = await fetchToken(db);
130140

131-
// // db.importData("csv", {data})
141+
const res = await db.importData(
142+
"airbyte-github",
143+
{
144+
credentials: {
145+
credentials: {
146+
personal_access_token: GITHUB_PAT_SECRET,
147+
},
148+
},
149+
params: {
150+
repository: "splitgraph/seafowl",
151+
start_date: "2021-06-01T00:00:00Z",
152+
},
153+
},
154+
{
155+
namespace: namespace,
156+
repository: "madatdata-test-github-ingestion",
157+
tables: [
158+
{
159+
name: "stargazers",
160+
options: {
161+
airbyte_cursor_field: ["starred_at"],
162+
airbyte_primary_key_field: [],
163+
},
164+
schema: [],
165+
},
166+
],
167+
}
168+
);
132169

133-
// // db.importData("airbyte-github"
134-
// });
135-
// });
170+
expect(res.response.status).toEqual(200);
171+
172+
// db.importData("airbyte-github", {}, })
173+
174+
// await db.importData("airbyte-github", { params: }
175+
176+
// db.importData("csv", {data})
177+
178+
// db.importData("airbyte-github"
179+
}, 60_000);
180+
}
181+
);
136182

137183
// Useful when writing initial tests against real server (where anon is allowed)
138184
// const _makeAnonymousDb = () => {

packages/db-splitgraph/plugins/importers/airbyte-github-plugin.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@ import type { AirbyteGithubCredentialsSchema } from "./generated/airbyte-github/
44

55
import { makeGeneratedImportPlugin } from "./splitgraph-generated-import-plugin";
66

7-
import type { ImportDestOptions } from "./base-import-plugin";
7+
// import type { ImportDestOptions } from "./base-import-plugin";
88
// import { SplitgraphImportCSVPlugin } from "./splitgraph-import-csv-plugin";
99

1010
export const AirbyteGithubImportPlugin = makeGeneratedImportPlugin<
1111
"airbyte-github",
1212
AirbyteGithubParamsSchema,
1313
AirbyteGithubTableParamsSchema,
14-
AirbyteGithubCredentialsSchema,
15-
ImportDestOptions<
16-
AirbyteGithubTableParamsSchema,
17-
AirbyteGithubCredentialsSchema
18-
>,
19-
{ params: AirbyteGithubParamsSchema }
14+
AirbyteGithubCredentialsSchema
2015
>("airbyte-github");
2116

2217
// const plugin = new AirbyteGithubImportPlugin({

packages/db-splitgraph/plugins/importers/base-import-plugin.ts

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,9 @@ import type {
1616
export type SplitgraphDestOptions = {
1717
namespace: string;
1818
repository: string;
19-
tableName: string;
20-
};
21-
22-
export interface ImportDestOptions<
23-
TableParamsSchema extends object,
24-
CredentialsSchema extends object
25-
> extends SplitgraphDestOptions {
26-
// TODO: note this is duplicated (idk why lol... think it's as a hack while supporting only 1 table)
27-
params?: TableParamsSchema;
28-
tableName: SplitgraphDestOptions["tableName"];
29-
30-
// TODO: support > 1 table
31-
tableParams?: TableParamsSchema;
32-
credentials?: CredentialsSchema;
33-
/* default private */
19+
/* default is public */
3420
initialPermissions?: StartExternalRepositoryLoadMutationVariables["initialPermissions"];
35-
}
21+
};
3622

3723
export interface SplitgraphImportPluginOptions {
3824
graphqlEndpoint: string;
@@ -55,6 +41,14 @@ const retryOptions = {
5541
exponentialOption: { maxInterval: MAX_BACKOFF_INTERVAL, multiplier: 2 },
5642
};
5743

44+
type ProvidedExternalLoadMutationVariables = Pick<
45+
StartExternalRepositoryLoadMutationVariables,
46+
"tables"
47+
> &
48+
Partial<
49+
Omit<StartExternalRepositoryLoadMutationVariables, "tables" | "pluginName">
50+
>;
51+
5852
export abstract class SplitgraphImportPlugin<
5953
PluginName extends string,
6054
/** The "params" schema for the plugin, i.e. provided by auto-generated type */
@@ -73,10 +67,7 @@ export abstract class SplitgraphImportPlugin<
7367
ConcreteImportDestOptions,
7468
ConcreteImportSourceOptions
7569
>,
76-
ConcreteImportDestOptions extends ImportDestOptions<
77-
PluginTableParamsSchema,
78-
PluginCredentialsSchema
79-
> = ImportDestOptions<PluginTableParamsSchema, PluginCredentialsSchema>,
70+
ConcreteImportDestOptions extends SplitgraphDestOptions = SplitgraphDestOptions,
8071
ConcreteImportSourceOptions extends object = Record<string, never>
8172
> implements
8273
ImportPlugin<PluginName>,
@@ -143,16 +134,15 @@ export abstract class SplitgraphImportPlugin<
143134
protected abstract makeLoadMutationVariables(
144135
sourceOptions: ConcreteImportSourceOptions,
145136
destOptions: ConcreteImportDestOptions
146-
): Pick<
147-
StartExternalRepositoryLoadMutationVariables,
148-
"params" | "tables" | "pluginName"
149-
>;
137+
): ProvidedExternalLoadMutationVariables;
138+
139+
// TODO: preview step should return available table names
150140

151141
private async startLoad(
152142
sourceOptions: ConcreteImportSourceOptions,
153143
destOptions: ConcreteImportDestOptions
154144
) {
155-
const { params, tables, pluginName } = this.makeLoadMutationVariables(
145+
const { tables, ...optionalVariables } = this.makeLoadMutationVariables(
156146
sourceOptions,
157147
destOptions
158148
);
@@ -170,6 +160,7 @@ export abstract class SplitgraphImportPlugin<
170160
$pluginName: String
171161
$params: JSON
172162
$credentialId: String
163+
$credentialData: JSON
173164
$sync: Boolean
174165
) {
175166
startExternalRepositoryLoad(
@@ -180,22 +171,27 @@ export abstract class SplitgraphImportPlugin<
180171
initialPermissions: $initialPermissions
181172
tables: $tables
182173
credentialId: $credentialId
174+
credentialData: $credentialData
183175
sync: $sync
184176
) {
185177
taskId
186178
}
187179
}
188180
`,
189181
{
190-
initialPermissions: destOptions.initialPermissions,
191-
// NOTE: Optional params are required for typescript, ignored when sent
192-
credentialId: undefined,
193-
sync: undefined,
194-
namespace: destOptions.namespace,
195-
repository: destOptions.repository,
196-
params,
182+
// Only required variable, but can be empty list to indicate loading all tables
197183
tables,
198-
pluginName,
184+
// Not required or passable, because it must be same as this.__name
185+
pluginName: this.__name,
186+
// These are required, but we default to destOptions.{namespace,repository}
187+
namespace: optionalVariables.namespace ?? destOptions.namespace,
188+
repository: optionalVariables.repository ?? destOptions.repository,
189+
// Truly optional variables
190+
params: optionalVariables.params ?? {},
191+
credentialData: optionalVariables.credentialData ?? undefined,
192+
initialPermissions: destOptions.initialPermissions ?? undefined,
193+
credentialId: optionalVariables.credentialId ?? undefined,
194+
sync: optionalVariables.sync ?? undefined,
199195
}
200196
);
201197
}
@@ -374,6 +370,9 @@ export abstract class SplitgraphImportPlugin<
374370
};
375371
}
376372

373+
// TODO: Clean this up to use an intermediate private member inside the derived class
374+
// instead of passing some magical object through some magical pipeline
375+
377376
/**
378377
* Derived classes should implement this method to perform any pre-import steps,
379378
* such as uploading a CSV file to object storage. It should return sourceOptions

packages/db-splitgraph/plugins/importers/splitgraph-generated-import-plugin.ts

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,64 @@
11
import type { ImportPlugin, WithOptionsInterface } from "@madatdata/base-db";
2-
import type { ImportDestOptions } from "./base-import-plugin";
32

43
import { SplitgraphImportPlugin } from "./base-import-plugin";
4+
import type { SplitgraphDestOptions } from "./base-import-plugin";
55
import type { SplitgraphImportPluginOptions } from "./base-import-plugin";
66

7-
interface GeneratedImportSourceOptions<PluginParamsSchema extends object> {
7+
import type { ExternalTableColumnInput } from "../../gql-client/unified-types";
8+
9+
export interface BaseGeneratedImportSourceOptions<
10+
PluginParamsSchema extends object
11+
> {
812
params: PluginParamsSchema;
13+
sync?: boolean;
14+
}
15+
16+
export interface GeneratedImportSourceOptionsWithInlineCredentialData<
17+
PluginParamsSchema extends object,
18+
PluginCredentialsSchema extends object
19+
> extends BaseGeneratedImportSourceOptions<PluginParamsSchema> {
20+
credentials: PluginCredentialsSchema;
21+
}
22+
23+
export interface GeneratedImportSourceOptionsWithSavedCredentialId<
24+
PluginParamsSchema extends object
25+
> extends BaseGeneratedImportSourceOptions<PluginParamsSchema> {
26+
credentialId: string;
27+
}
28+
29+
export type GeneratedImportSourceOptions<
30+
PluginParamsSchema extends object,
31+
PluginCredentialsSchema extends object
32+
> =
33+
| GeneratedImportSourceOptionsWithInlineCredentialData<
34+
PluginParamsSchema,
35+
PluginCredentialsSchema
36+
>
37+
| GeneratedImportSourceOptionsWithSavedCredentialId<PluginParamsSchema>;
38+
39+
export interface GeneratedImportDestOptions<TableParamsSchema extends object>
40+
extends SplitgraphDestOptions {
41+
tables?: {
42+
name: string;
43+
options: TableParamsSchema;
44+
/**
45+
* Array containing which columns to include in the ingestion. Set to an
46+
* empty array (`[]`) to default to including all columns.
47+
* */
48+
schema: ExternalTableColumnInput[];
49+
}[];
950
}
1051

1152
export function makeGeneratedImportPlugin<
1253
PluginName extends string,
1354
ParamsSchema extends object,
1455
TableParamsSchema extends object,
1556
CredentialsSchema extends object,
16-
ConcreteImportDestOptions extends ImportDestOptions<
17-
TableParamsSchema,
57+
ConcreteImportDestOptions extends GeneratedImportDestOptions<TableParamsSchema> = GeneratedImportDestOptions<TableParamsSchema>,
58+
ConcreteImportSourceOptions extends GeneratedImportSourceOptions<
59+
ParamsSchema,
1860
CredentialsSchema
19-
> = ImportDestOptions<TableParamsSchema, CredentialsSchema>,
20-
ConcreteImportSourceOptions extends GeneratedImportSourceOptions<ParamsSchema> = GeneratedImportSourceOptions<ParamsSchema>
61+
> = GeneratedImportSourceOptions<ParamsSchema, CredentialsSchema>
2162
>(
2263
pluginName: PluginName
2364
): new (opts: SplitgraphImportPluginOptions) => ImportPlugin<
@@ -51,21 +92,22 @@ export function makeGeneratedImportPlugin<
5192
sourceOptions: ConcreteImportSourceOptions,
5293
destOptions: ConcreteImportDestOptions
5394
) {
95+
// NOTE: only need to return variables that aren't already defaulted e.g. from destOptions
96+
// which is why we can skip namespace, repository, and pluginName
5497
return {
5598
params: JSON.stringify({
5699
...sourceOptions.params,
57100
}),
58-
tables: [
59-
{
60-
name: destOptions.tableName,
61-
options: JSON.stringify({
62-
...destOptions.tableParams,
63-
}),
64-
// TODO: allow user to specify schema in destOptions
65-
schema: [],
66-
},
67-
],
68-
pluginName: "csv",
101+
tables: destOptions.tables ?? [],
102+
credentialData:
103+
"credentials" in sourceOptions
104+
? JSON.stringify(sourceOptions.credentials)
105+
: undefined,
106+
credentialId:
107+
"credentialId" in sourceOptions
108+
? sourceOptions.credentialId
109+
: undefined,
110+
sync: "sync" in sourceOptions ? sourceOptions.sync : undefined,
69111
};
70112
}
71113
}

0 commit comments

Comments
 (0)