Skip to content

Commit

Permalink
Add docker image tag of actor definition version to ConnectorTitleBlo…
Browse files Browse the repository at this point in the history
…ck on actor pages (#7101)

Co-authored-by: Joey Marshment-Howell <josephkmh@users.noreply.github.com>
  • Loading branch information
Ella Rohm-Ensing and josephkmh committed Jun 27, 2023
1 parent 6968786 commit 7ec4944
Show file tree
Hide file tree
Showing 12 changed files with 288 additions and 8 deletions.
56 changes: 55 additions & 1 deletion airbyte-api/src/main/openapi/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ tags:
description: Interactions with attempt related resources.
- name: state
description: Interactions with state related resources.
- name: actor_definition_version
description: Interactions with actor definition version related resources.

paths:
/v1/workspaces/create:
Expand Down Expand Up @@ -519,6 +521,27 @@ paths:
$ref: "#/components/responses/NotFoundResponse"
"422":
$ref: "#/components/responses/InvalidInputResponse"
/v1/actor_definition_versions/get_for_source:
post:
tags:
- actor_definition_version
summary: Get actor definition version for a source.
operationId: getActorDefinitionVersionForSourceId
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/SourceIdRequestBody"
required: true
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: "#/components/schemas/ActorDefinitionVersionRead"
"404":
$ref: "#/components/responses/NotFoundResponse"
/v1/source_definition_specifications/get:
post:
tags:
Expand Down Expand Up @@ -1257,6 +1280,27 @@ paths:
$ref: "#/components/responses/NotFoundResponse"
"422":
$ref: "#/components/responses/InvalidInputResponse"
/v1/actor_definition_versions/get_for_destination:
post:
tags:
- actor_definition_version
summary: Get actor definition version for a destination.
operationId: getActorDefinitionVersionForDestinationId
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/DestinationIdRequestBody"
required: true
responses:
"200":
description: Successful operation
content:
application/json:
schema:
$ref: "#/components/schemas/ActorDefinitionVersionRead"
"404":
$ref: "#/components/responses/NotFoundResponse"
/v1/destination_definition_specifications/get:
post:
tags:
Expand Down Expand Up @@ -4040,7 +4084,17 @@ components:
type: string
destinationName:
type: string
# SOURCE / DESTINATION RELEASE STAGE ENUM
# ACTOR DEFINITION VERSION
ActorDefinitionVersionRead:
type: object
required:
- dockerRepository
- dockerImageTag
properties:
dockerRepository:
type: string
dockerImageTag:
type: string
ReleaseStage:
type: string
enum:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.commons.server.handlers;

import io.airbyte.api.model.generated.ActorDefinitionVersionRead;
import io.airbyte.api.model.generated.DestinationIdRequestBody;
import io.airbyte.api.model.generated.SourceIdRequestBody;
import io.airbyte.config.ActorDefinitionVersion;
import io.airbyte.config.DestinationConnection;
import io.airbyte.config.SourceConnection;
import io.airbyte.config.StandardDestinationDefinition;
import io.airbyte.config.StandardSourceDefinition;
import io.airbyte.config.persistence.ActorDefinitionVersionHelper;
import io.airbyte.config.persistence.ConfigNotFoundException;
import io.airbyte.config.persistence.ConfigRepository;
import io.airbyte.validation.json.JsonValidationException;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.io.IOException;

/**
* DestinationHandler. Javadocs suppressed because api docs should be used as source of truth.
*/
@SuppressWarnings({"MissingJavadocMethod", "ParameterName"})
@Singleton
public class ActorDefinitionVersionHandler {

private final ConfigRepository configRepository;
private final ActorDefinitionVersionHelper actorDefinitionVersionHelper;

@Inject
public ActorDefinitionVersionHandler(final ConfigRepository configRepository,
final ActorDefinitionVersionHelper actorDefinitionVersionHelper) {
this.configRepository = configRepository;
this.actorDefinitionVersionHelper = actorDefinitionVersionHelper;
}

public ActorDefinitionVersionRead getActorDefinitionVersionForSourceId(final SourceIdRequestBody sourceIdRequestBody)
throws JsonValidationException, ConfigNotFoundException, IOException {
final SourceConnection sourceConnection = configRepository.getSourceConnection(sourceIdRequestBody.getSourceId());
final StandardSourceDefinition sourceDefinition = configRepository.getSourceDefinitionFromSource(sourceConnection.getSourceId());
final ActorDefinitionVersion actorDefinitionVersion =
actorDefinitionVersionHelper.getSourceVersion(sourceDefinition, sourceConnection.getWorkspaceId(), sourceConnection.getSourceId());
return createActorDefinitionVersionRead(actorDefinitionVersion);
}

public ActorDefinitionVersionRead getActorDefinitionVersionForDestinationId(final DestinationIdRequestBody destinationIdRequestBody)
throws JsonValidationException, ConfigNotFoundException, IOException {
final DestinationConnection destinationConnection = configRepository.getDestinationConnection(destinationIdRequestBody.getDestinationId());
final StandardDestinationDefinition destinationDefinition =
configRepository.getDestinationDefinitionFromDestination(destinationConnection.getDestinationId());
final ActorDefinitionVersion actorDefinitionVersion = actorDefinitionVersionHelper.getDestinationVersion(destinationDefinition,
destinationConnection.getWorkspaceId(), destinationConnection.getDestinationId());
return createActorDefinitionVersionRead(actorDefinitionVersion);
}

private ActorDefinitionVersionRead createActorDefinitionVersionRead(final ActorDefinitionVersion actorDefinitionVersion) {
return new ActorDefinitionVersionRead()
.dockerRepository(actorDefinitionVersion.getDockerRepository())
.dockerImageTag(actorDefinitionVersion.getDockerImageTag());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -468,16 +468,16 @@ protected static SourceSnippetRead toSourceSnippetRead(final SourceConnection so
@VisibleForTesting
JsonNode hydrateOAuthResponseSecret(final String secretId) {
final SecretCoordinate secretCoordinate = SecretCoordinate.fromFullCoordinate(secretId);
JsonNode secret = secretsRepositoryReader.fetchSecret(secretCoordinate);
CompleteOAuthResponse completeOAuthResponse = Jsons.object(secret, CompleteOAuthResponse.class);
final JsonNode secret = secretsRepositoryReader.fetchSecret(secretCoordinate);
final CompleteOAuthResponse completeOAuthResponse = Jsons.object(secret, CompleteOAuthResponse.class);
return Jsons.jsonNode(completeOAuthResponse.getAuthPayload());
}

@VisibleForTesting
JsonNode hydrateConnectionConfiguration(final UUID sourceDefinitionId,
final UUID workspaceId,
final String secretId,
JsonNode dehydratedConnectionConfiguration)
final JsonNode dehydratedConnectionConfiguration)
throws JsonValidationException, ConfigNotFoundException, IOException {
final JsonNode hydratedSecret = hydrateOAuthResponseSecret(secretId);
final ConnectorSpecification spec =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2023 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.server.apis;

import static io.airbyte.commons.auth.AuthRoleConstants.AUTHENTICATED_USER;

import io.airbyte.api.generated.ActorDefinitionVersionApi;
import io.airbyte.api.model.generated.ActorDefinitionVersionRead;
import io.airbyte.api.model.generated.DestinationIdRequestBody;
import io.airbyte.api.model.generated.SourceIdRequestBody;
import io.airbyte.commons.server.handlers.ActorDefinitionVersionHandler;
import io.airbyte.commons.server.scheduling.AirbyteTaskExecutors;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;

@SuppressWarnings("MissingJavadocType")
@Controller("/api/v1/actor_definition_versions")
@Secured(SecurityRule.IS_AUTHENTICATED)
public class ActorDefinitionVersionApiController implements ActorDefinitionVersionApi {

private final ActorDefinitionVersionHandler actorDefinitionVersionHandler;

public ActorDefinitionVersionApiController(final ActorDefinitionVersionHandler actorDefinitionVersionHandler) {
this.actorDefinitionVersionHandler = actorDefinitionVersionHandler;
}

@Post("/get_for_source")
@Secured({AUTHENTICATED_USER})
@ExecuteOn(AirbyteTaskExecutors.IO)
@Override
public ActorDefinitionVersionRead getActorDefinitionVersionForSourceId(final SourceIdRequestBody sourceIdRequestBody) {
return ApiHelper.execute(() -> actorDefinitionVersionHandler.getActorDefinitionVersionForSourceId(sourceIdRequestBody));
}

@Post("/get_for_destination")
@Secured({AUTHENTICATED_USER})
@ExecuteOn(AirbyteTaskExecutors.IO)
@Override
public ActorDefinitionVersionRead getActorDefinitionVersionForDestinationId(final DestinationIdRequestBody destinationIdRequestBody) {
return ApiHelper.execute(() -> actorDefinitionVersionHandler.getActorDefinitionVersionForDestinationId(destinationIdRequestBody));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2023 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.server.apis;

import io.airbyte.api.model.generated.ActorDefinitionVersionRead;
import io.airbyte.api.model.generated.DestinationIdRequestBody;
import io.airbyte.api.model.generated.SourceIdRequestBody;
import io.airbyte.commons.json.Jsons;
import io.airbyte.config.persistence.ConfigNotFoundException;
import io.airbyte.validation.json.JsonValidationException;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpStatus;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

/**
* Test class for {@link ActorDefinitionVersionApiController}.
*/
@SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert")
class ActorDefinitionVersionApiTest extends BaseControllerTest {

@Test
void testGetActorDefinitionForSource() throws JsonValidationException, ConfigNotFoundException, IOException {
Mockito.when(actorDefinitionVersionHandler.getActorDefinitionVersionForSourceId(Mockito.any()))
.thenReturn(new ActorDefinitionVersionRead())
.thenThrow(new ConfigNotFoundException("", ""));
final String path = "/api/v1/actor_definition_versions/get_for_source";
testEndpointStatus(
HttpRequest.POST(path, Jsons.serialize(new SourceIdRequestBody())),
HttpStatus.OK);
testErrorEndpointStatus(
HttpRequest.POST(path, Jsons.serialize(new SourceIdRequestBody())),
HttpStatus.NOT_FOUND);
}

@Test
void testGetActorDefinitionForDestination() throws JsonValidationException, ConfigNotFoundException, IOException {
Mockito.when(actorDefinitionVersionHandler.getActorDefinitionVersionForDestinationId(Mockito.any()))
.thenReturn(new ActorDefinitionVersionRead())
.thenThrow(new ConfigNotFoundException("", ""));
final String path = "/api/v1/actor_definition_versions/get_for_destination";
testEndpointStatus(
HttpRequest.POST(path, Jsons.serialize(new DestinationIdRequestBody())),
HttpStatus.OK);
testErrorEndpointStatus(
HttpRequest.POST(path, Jsons.serialize(new DestinationIdRequestBody())),
HttpStatus.NOT_FOUND);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;

import io.airbyte.analytics.TrackingClient;
import io.airbyte.commons.server.handlers.ActorDefinitionVersionHandler;
import io.airbyte.commons.server.handlers.AttemptHandler;
import io.airbyte.commons.server.handlers.ConnectionsHandler;
import io.airbyte.commons.server.handlers.DestinationDefinitionsHandler;
Expand Down Expand Up @@ -66,6 +67,14 @@
@SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod")
abstract class BaseControllerTest {

ActorDefinitionVersionHandler actorDefinitionVersionHandler = Mockito.mock(ActorDefinitionVersionHandler.class);

@MockBean(ActorDefinitionVersionHandler.class)
@Replaces(ActorDefinitionVersionHandler.class)
ActorDefinitionVersionHandler mmActorDefinitionVersionHandler() {
return actorDefinitionVersionHandler;
}

AttemptHandler attemptHandler = Mockito.mock(AttemptHandler.class);

@MockBean(AttemptHandler.class)
Expand Down
17 changes: 15 additions & 2 deletions airbyte-webapp/src/components/connector/ConnectorTitleBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { FormattedMessage } from "react-intl";

import { ConnectorIcon } from "components/common/ConnectorIcon";
import { ReleaseStageBadge } from "components/ReleaseStageBadge";
import { FlexContainer } from "components/ui/Flex";
import { Heading } from "components/ui/Heading";
import { Text } from "components/ui/Text";

import { ConnectorDefinition } from "core/domain/connector";
import { DestinationRead, SourceRead } from "core/request/AirbyteClient";
import { DestinationRead, SourceRead, ActorDefinitionVersionRead } from "core/request/AirbyteClient";

import styles from "./ConnectorTitleBlock.module.scss";

Expand All @@ -14,12 +16,23 @@ type Connector = SourceRead | DestinationRead;
interface ConnectorTitleBlockProps<T extends Connector> {
connector: T;
connectorDefinition: ConnectorDefinition;
actorDefinitionVersion: ActorDefinitionVersionRead;
}

export const ConnectorTitleBlock = <T extends Connector>({
connector,
connectorDefinition,
actorDefinitionVersion,
}: ConnectorTitleBlockProps<T>) => {
const titleInfo =
connectorDefinition.releaseStage === "custom" ? (
`${connectorDefinition.name}`
) : (
<FormattedMessage
id="connector.connectorNameAndVersion"
values={{ connectorName: connectorDefinition.name, version: actorDefinitionVersion.dockerImageTag }}
/>
);
return (
<FlexContainer alignItems="center">
<ConnectorIcon icon={connector.icon} className={styles.icon} />
Expand All @@ -28,7 +41,7 @@ export const ConnectorTitleBlock = <T extends Connector>({
{connector.name}
</Heading>
<FlexContainer alignItems="center">
<Text color="grey">{connectorDefinition.name}</Text>
<Text color="grey">{titleInfo}</Text>
<ReleaseStageBadge stage={connectorDefinition.releaseStage} />
</FlexContainer>
</FlexContainer>
Expand Down
24 changes: 24 additions & 0 deletions airbyte-webapp/src/core/api/hooks/actorDefinitionVersions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { SCOPE_WORKSPACE } from "services/Scope";

import {
getActorDefinitionVersionForDestinationId,
getActorDefinitionVersionForSourceId,
} from "../generated/AirbyteClient";
import { useRequestOptions } from "../useRequestOptions";
import { useSuspenseQuery } from "../useSuspenseQuery";

export function useSourceDefinitionVersion(sourceId: string) {
const requestOptions = useRequestOptions();

return useSuspenseQuery([SCOPE_WORKSPACE, "actorDefinitionVersion", sourceId], () =>
getActorDefinitionVersionForSourceId({ sourceId }, requestOptions)
);
}

export function useDestinationDefinitionVersion(destinationId: string) {
const requestOptions = useRequestOptions();

return useSuspenseQuery([SCOPE_WORKSPACE, "actorDefinitionVersion", destinationId], () =>
getActorDefinitionVersionForDestinationId({ destinationId }, requestOptions)
);
}
1 change: 1 addition & 0 deletions airbyte-webapp/src/core/api/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./actorDefinitionVersions";
export * from "./geographies";
export * from "./health";
export * from "./jobs";
Expand Down
1 change: 1 addition & 0 deletions airbyte-webapp/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@
"connector.releaseStage.custom": "Custom",
"connector.releaseStage.generally_available": "GA",
"connector.releaseStage.generallyAvailable.expanded": "Generally Available",
"connector.connectorNameAndVersion": "{connectorName} v{version}",
"connector.releaseStage.custom.description": "<b>Custom connectors</b> are added to the workspace manually by the user. Support is not provided.",
"connector.releaseStage.alpha.description": "<b>Alpha connectors</b> are in development and support is not provided.",
"connector.releaseStage.beta.description": "<b>Beta connectors</b> are in development but stable and reliable and support is provided.",
Expand Down
Loading

0 comments on commit 7ec4944

Please sign in to comment.