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

Generate connector builder api client #18274

Merged
merged 9 commits into from
Oct 24, 2022
3 changes: 2 additions & 1 deletion airbyte-webapp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ yarn-error.log*

storybook-static/

# Ignore generated API client, since it's automatically generated
# Ignore generated API clients, since they're automatically generated
/src/core/request/AirbyteClient.ts
/src/core/request/ConnectorBuilderClient.ts
21 changes: 21 additions & 0 deletions airbyte-webapp/orval.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,25 @@ export default defineConfig({
},
},
},
connectorBuilder: {
input: "../connector-builder-server/src/main/openapi/openapi.yaml",
output: {
target: "./src/core/request/ConnectorBuilderClient.ts",
prettier: true,
override: {
header: (info) => [
`eslint-disable`,
`Generated by orval 🍺`,
`Do not edit manually. Run "npm run generate-client" instead.`,
...(info.title ? [info.title] : []),
...(info.description ? [info.description] : []),
...(info.version ? [`OpenAPI spec version: ${info.version}`] : []),
],
mutator: {
path: "./src/core/request/apiOverride.ts",
name: "apiOverride",
},
},
},
},
});
1 change: 1 addition & 0 deletions airbyte-webapp/src/config/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const defaultConfig: Config = {
healthCheckInterval: 20000,
version: "dev",
apiUrl: `${window.location.protocol}//${window.location.hostname}:8001/api`,
connectorBuilderApiUrl: `${window.location.protocol}//${window.location.hostname}:8080/`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8080 seems like a very common port

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to change it to whatever port we think is appropriate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use 8003, since it seems like that is not currently used by anything else in OSS or Cloud (8001 is used by airbyte-server, and 8002 is used by the airyte-cloud-server in Cloud)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way, I'm just going to commit this as is for now, since we can easily change this later

integrationUrl: "/docs",
oauthRedirectUrl: `${window.location.protocol}//${window.location.host}`,
};
Expand Down
1 change: 1 addition & 0 deletions airbyte-webapp/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ declare global {
export interface Config {
segment: { token: string; enabled: boolean };
apiUrl: string;
connectorBuilderApiUrl: string;
oauthRedirectUrl: string;
healthCheckInterval: number;
version?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
StreamRead,
StreamReadRequestBody,
StreamsListRead,
StreamsListRequestBody,
} from "core/request/ConnectorBuilderClient";

import { AirbyteRequestService } from "../../request/AirbyteRequestService";

export class ConnectorBuilderRequestService extends AirbyteRequestService {
public readStream(readParams: StreamReadRequestBody): Promise<StreamRead> {
// TODO: uncomment this and remove mock responses once there is a real API to call
// return readStream(readParams, this.requestOptions);
console.log("------------");
console.log(`Stream: ${readParams.stream}`);
console.log(`Connector manifest:\n${JSON.stringify(readParams.manifest)}`);
console.log(`Config:\n${JSON.stringify(readParams.config)}`);
return new Promise((resolve) => setTimeout(resolve, 200)).then(() => {
return {
logs: [
{ level: "INFO", message: "Syncing stream: rates " },
{ level: "INFO", message: "Setting state of rates stream to {'date': '2022-09-25'}" },
],
slices: [
{
sliceDescriptor: { start: "Jan 1, 2022", end: "Jan 2, 2022" },
state: {
type: "STREAM",
stream: { stream_descriptor: { name: readParams.stream }, stream_state: { date: "2022-09-26" } },
data: { rates: { date: "2022-09-26" } },
},
pages: [
{
records: [
{
stream: readParams.stream,
data: {
id: "dp_123",
object: readParams.stream,
amount: 2000,
balance_transaction: "txn_123",
},
},
],
request: {
url: "https://api.com/path",
},
response: {
status: 200,
},
},
],
},
],
};
});
}

public listStreams(listParams: StreamsListRequestBody): Promise<StreamsListRead> {
// TODO: uncomment this and remove mock responses once there is a real API to call
// return listStreams(listParams, this.requestOptions);
console.log(`Received listStreams body: ${JSON.stringify(listParams)}`);
return new Promise((resolve) => setTimeout(resolve, 200)).then(() => {
return {
streams: [
{
name: "disputes",
url: "https://api.com/disputes",
},
{
name: "transactions",
url: "https://api.com/transactions",
},
{
name: "users",
url: "https://api.com/users",
},
],
};
});
}
}
2 changes: 1 addition & 1 deletion airbyte-webapp/src/pages/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import MainView from "views/layout/MainView";

import { WorkspaceRead } from "../core/request/AirbyteClient";
import ConnectionPage from "./ConnectionPage";
import { ConnectorBuilderPage } from "./connector-builder/ConnectorBuilderPage";
import { ConnectorBuilderPage } from "./ConnectorBuilderPage/ConnectorBuilderPage";
import DestinationPage from "./DestinationPage";
import OnboardingPage from "./OnboardingPage";
import PreferencesPage from "./PreferencesPage";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useQuery } from "react-query";

import { useConfig } from "config";
import { ConnectorBuilderRequestService } from "core/domain/connectorBuilder/ConnectorBuilderRequestService";
import {
StreamReadRequestBody,
StreamReadRequestBodyConfig,
StreamReadRequestBodyManifest,
StreamsListRequestBody,
} from "core/request/ConnectorBuilderClient";
import { useSuspenseQuery } from "services/connector/useSuspenseQuery";
import { useDefaultRequestMiddlewares } from "services/useDefaultRequestMiddlewares";
import { useInitService } from "services/useInitService";

const connectorBuilderKeys = {
all: ["connectorBuilder"] as const,
read: (streamName: string, manifest: StreamReadRequestBodyManifest, config: StreamReadRequestBodyConfig) =>
[...connectorBuilderKeys.all, "read", { streamName, manifest, config }] as const,
list: (manifest: StreamReadRequestBodyManifest) => [...connectorBuilderKeys.all, "list", { manifest }] as const,
};

function useConnectorBuilderService() {
const config = useConfig();
const middlewares = useDefaultRequestMiddlewares();
return useInitService(
() => new ConnectorBuilderRequestService(config.connectorBuilderApiUrl, middlewares),
[config.connectorBuilderApiUrl, middlewares]
);
}

export const useReadStream = (params: StreamReadRequestBody) => {
const service = useConnectorBuilderService();

return useQuery(
connectorBuilderKeys.read(params.stream, params.manifest, params.config),
() => service.readStream(params),
{ refetchOnWindowFocus: false, enabled: false }
);
};

export const useListStreams = (params: StreamsListRequestBody) => {
const service = useConnectorBuilderService();

return useSuspenseQuery(connectorBuilderKeys.list(params.manifest), () => service.listStreams(params));
};
72 changes: 47 additions & 25 deletions connector-builder-server/src/main/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ paths:
/v1/stream/read:
post:
summary: Reads a specific stream in the source. TODO in a later phase - only read a single slice of data.
operationId: readStream
requestBody:
content:
application/json:
Expand All @@ -37,7 +38,8 @@ paths:
$ref: "#/components/responses/InvalidInputResponse"
/v1/streams/list:
post:
summary: List all streams present in the connector definition, along with their specific request URLs
summary: List all streams present in the connector manifest, along with their specific request URLs
operationId: listStreams
requestBody:
content:
application/json:
Expand All @@ -61,53 +63,71 @@ components:
StreamRead:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@girarda @brianjlai @sherifnada
I've made a few changes to the StreamRead object here, if you could take a look. The main thing is that I've split out airbyteMessages into separate fields for logs, records, and state.

The reasoning behind this is mainly coming from not being able to pull in the AirbyteProtocol struct into this openAPI spec, so we will need the connector-builder-server to filter out the various types of AirbyteMessages into separate fields like this so that the frontend knows what to do with them.

I pulled logs up to the top level of this response, since I feel like users will just want to view all of the logs for the entire read operation (and that also matches the "logs tray" design in the prototype), rather than seeing them split up by page. But let me know if you disagree.

I also pulled state up to the slice-level of this response, since we expect that there should be one state message for each slice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with moving the state to the slice-level.

No strong opinion on whether the the logs should be at the page-level or global. I think global is fine

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also seems fine to me. We sometimes have to deal w/ consecutive state messages (or the case where slices have no records) so maybe this approach works better anyway so the underlying python server can obfuscate the complexity of formatting what some of these outputs might look like

type: object
required:
- logs
- slices
properties:
logs:
type: array
description: The LOG AirbyteMessages that were emitted during the read of this slice
items:
type: object
# $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteLogMessage"
slices:
type: array
description: The stream slices returned from the read command
description: The stream slices returned from the read command. If no stream slicer is configured, this should contain a single item containing all of the results.
items:
type: object
required:
- sliceDescriptor
- pages
properties:
sliceDescriptor:
type: object
description: 'An object describing the current slice, e.g. {start_time: "2021-01-01", end_time: "2021-01-31"}'
pages:
type: array
description: The pages returned from the read command
description: The pages returned from the read command. If no pagination is configured, this should contain a single item containing all of the results.
items:
type: object
required:
- airbyteMessages
- records
- request
- response
properties:
airbyteMessages:
records:
type: array
description: The RECORD/STATE/LOG AirbyteMessages coming from the read operation for this page
description: The RECORD AirbyteMessages coming from the read operation for this page
items:
$ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteMessage"
type: object
# $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteRecordMessage"
request:
$ref: "#/components/schemas/HttpRequest"
response:
$ref: "#/components/schemas/HttpResponse"
sliceDescriptor:
type: object
description: 'An object describing the current slice, e.g. {start_time: "2021-01-01", end_time: "2021-01-31"}. This can be omitted if a stream slicer is not configured.'
state:
type: object
description: The STATE AirbyteMessage emitted at the end of this slice. This can be omitted if a stream slicer is not configured.
# $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteStateMessage"
StreamReadRequestBody:
type: object
required:
- definition
- manifest
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several changes in this spec to rename "connector definition" to "manifest", following this decision made in slack: https://airbytehq-team.slack.com/archives/C03TP6W8081/p1666214768391589

- stream
- config
properties:
definition:
$ref: "#/components/schemas/ConnectorDefinitionBody"
description: The config-based connector definition contents
manifest:
type: object
description: The config-based connector manifest contents
# $ref: "#/components/schemas/ConnectorManifest"
stream:
type: string
description: Name of the stream to read
config:
type: object
description: The config blob containing the user inputs for testing
Comment on lines +124 to +126
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that this was missing from the /stream/read endpoint, so I added it here

state:
$ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteStateMessage"
type: object
description: The AirbyteStateMessage object to use as the starting state for this read
# $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteStateMessage"
# --- Potential addition for a later phase ---
# numPages:
# type: integer
Expand Down Expand Up @@ -144,18 +164,20 @@ components:
headers:
type: object
description: The headers of the HTTP response, if any
ConnectorDefinitionBody:
$ref: ../../../../airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json
AirbyteProtocol:
$ref: ../../../../airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml
# --- Commenting out for now since they do not work with our orval openapi client generator ---
# ConnectorManifest:
# $ref: ../../../../airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json
# AirbyteProtocol:
# $ref: ../../../../airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml
StreamsListRequestBody:
type: object
required:
- definition
- manifest
properties:
definition:
$ref: "#/components/schemas/ConnectorDefinitionBody"
description: The config-based connector definition contents
manifest:
type: object
description: The config-based connector manifest contents
# $ref: "#/components/schemas/ConnectorManifest"
StreamsListRead:
type: object
required:
Expand All @@ -165,7 +187,7 @@ components:
type: array
items:
type: object
description: The stream names present in the connector definition
description: The stream names present in the connector manifest
required:
- name
- url
Expand Down