Skip to content

Commit

Permalink
Delete Workspace UI
Browse files Browse the repository at this point in the history
  • Loading branch information
spugachev committed Oct 10, 2023
1 parent c4e3f45 commit be5d438
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 26 deletions.
8 changes: 8 additions & 0 deletions lib/chatbot-api/functions/api-handler/routes/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ def workspace(workspace_id: str):
return {"ok": True, "data": ret_value}


@router.delete("/workspaces/<workspace_id>")
@tracer.capture_method
def workspace(workspace_id: str):
genai_core.workspaces.delete_workspace(workspace_id)

return {"ok": True}


@router.put("/workspaces")
@tracer.capture_method
def create_workspace():
Expand Down
6 changes: 6 additions & 0 deletions lib/chatbot-api/rest-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export class RestApi extends Construct {
props.ragEngines?.documentsByCompountKeyIndexName ?? "",
SAGEMAKER_RAG_MODELS_ENDPOINT:
props.ragEngines?.sageMakerRagModelsEndpoint?.attrEndpointName ?? "",
DELETE_WORKSPACE_WORKFLOW_ARN:
props.ragEngines?.deleteWorkspaceWorkflow?.stateMachineArn ?? "",
CREATE_AURORA_WORKSPACE_WORKFLOW_ARN:
props.ragEngines?.auroraPgVector?.createAuroraWorkspaceWorkflow
?.stateMachineArn ?? "",
Expand Down Expand Up @@ -206,6 +208,10 @@ export class RestApi extends Construct {
props.ragEngines.websiteCrawlingWorkflow.grantStartExecution(apiHandler);
}

if (props.ragEngines?.deleteWorkspaceWorkflow) {
props.ragEngines.deleteWorkspaceWorkflow.grantStartExecution(apiHandler);
}

if (props.ragEngines?.sageMakerRagModelsEndpoint) {
apiHandler.addToRolePolicy(
new iam.PolicyStatement({
Expand Down
12 changes: 12 additions & 0 deletions lib/rag-engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DataImport } from "./data-import";
import { RagDynamoDBTables } from "./rag-dynamodb-tables";
import { OpenSearchVector } from "./opensearch-vector";
import { KendraRetrieval } from "./kendra-retrieval";
import { Workspaces } from "./workspaces";
import * as sagemaker from "aws-cdk-lib/aws-sagemaker";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
Expand All @@ -30,6 +31,7 @@ export class RagEngines extends Construct {
public readonly sageMakerRagModelsEndpoint: sagemaker.CfnEndpoint;
public readonly fileImportWorkflow?: sfn.StateMachine;
public readonly websiteCrawlingWorkflow?: sfn.StateMachine;
public readonly deleteWorkspaceWorkflow?: sfn.StateMachine;

constructor(scope: Construct, id: string, props: RagEnginesProps) {
super(scope, id);
Expand Down Expand Up @@ -86,6 +88,15 @@ export class RagEngines extends Construct {
kendraRetrieval: kendraRetrieval ?? undefined,
});

const workspaces = new Workspaces(this, "Workspaces", {
shared: props.shared,
config: props.config,
ragDynamoDBTables: tables,
auroraPgVector: auroraPgVector ?? undefined,
openSearch: openSearchVector ?? undefined,
kendraRetrieval: kendraRetrieval ?? undefined,
});

this.auroraPgVector = auroraPgVector;
this.openSearchVector = openSearchVector;
this.kendraRetrieval = kendraRetrieval;
Expand All @@ -100,5 +111,6 @@ export class RagEngines extends Construct {
tables.documentsByCompountKeyIndexName;
this.fileImportWorkflow = dataImport.fileImportWorkflow;
this.websiteCrawlingWorkflow = dataImport.websiteCrawlingWorkflow;
this.deleteWorkspaceWorkflow = workspaces.deleteWorkspaceWorkflow;
}
}
128 changes: 128 additions & 0 deletions lib/rag-engines/workspaces/delete-wrokspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import * as path from "path";
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { SystemConfig } from "../../shared/types";
import { Shared } from "../../shared";
import { RagDynamoDBTables } from "../rag-dynamodb-tables";
import { OpenSearchVector } from "../opensearch-vector";
import { KendraRetrieval } from "../kendra-retrieval";
import { AuroraPgVector } from "../aurora-pgvector";
import * as sfn from "aws-cdk-lib/aws-stepfunctions";
import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as logs from "aws-cdk-lib/aws-logs";

export interface DeleteWorkspaceProps {
readonly config: SystemConfig;
readonly shared: Shared;
readonly ragDynamoDBTables: RagDynamoDBTables;
readonly auroraPgVector?: AuroraPgVector;
readonly openSearch?: OpenSearchVector;
readonly kendraRetrieval?: KendraRetrieval;
}

export class DeleteWorkspace extends Construct {
public readonly stateMachine?: sfn.StateMachine;

constructor(scope: Construct, id: string, props: DeleteWorkspaceProps) {
super(scope, id);

const deleteFunction = new lambda.Function(
this,
"DeleteWorkspaceFunction",
{
vpc: props.shared.vpc,
code: lambda.Code.fromAsset(
path.join(__dirname, "./functions/delete-workspace-workflow/delete")
),
runtime: props.shared.pythonRuntime,
architecture: props.shared.lambdaArchitecture,
handler: "index.lambda_handler",
layers: [
props.shared.powerToolsLayer,
props.shared.commonLayer.layer,
props.shared.pythonSDKLayer,
],
timeout: cdk.Duration.minutes(5),
logRetention: logs.RetentionDays.ONE_WEEK,
environment: {
...props.shared.defaultEnvironmentVariables,
AURORA_DB_SECRET_ID: props.auroraPgVector?.database.secret
?.secretArn as string,
WORKSPACES_TABLE_NAME:
props.ragDynamoDBTables.workspacesTable.tableName,
WORKSPACES_BY_OBJECT_TYPE_INDEX_NAME:
props.ragDynamoDBTables.workspacesByObjectTypeIndexName,
},
}
);

if (props.auroraPgVector) {
props.auroraPgVector.database.secret?.grantRead(deleteFunction);
props.auroraPgVector.database.connections.allowDefaultPortFrom(
deleteFunction
);
}

props.ragDynamoDBTables.workspacesTable.grantReadWriteData(deleteFunction);

const handleError = new tasks.DynamoUpdateItem(this, "HandleError", {
table: props.ragDynamoDBTables.workspacesTable,
key: {
workspace_id: tasks.DynamoAttributeValue.fromString(
sfn.JsonPath.stringAt("$.workspace_id")
),
object_type: tasks.DynamoAttributeValue.fromString("workspace"),
},
updateExpression: "set #status = :error",
expressionAttributeNames: {
"#status": "status",
},
expressionAttributeValues: {
":error": tasks.DynamoAttributeValue.fromString("error"),
},
}).next(
new sfn.Fail(this, "Fail", {
cause: "Workspace deletion failed",
})
);

const setDeleting = new tasks.DynamoUpdateItem(this, "SetDeleting", {
table: props.ragDynamoDBTables.workspacesTable,
key: {
workspace_id: tasks.DynamoAttributeValue.fromString(
sfn.JsonPath.stringAt("$.workspace_id")
),
object_type: tasks.DynamoAttributeValue.fromString("workspace"),
},
updateExpression: "set #status=:statusValue",
expressionAttributeNames: {
"#status": "status",
},
expressionAttributeValues: {
":statusValue": tasks.DynamoAttributeValue.fromString("deleting"),
},
resultPath: sfn.JsonPath.DISCARD,
});

const deleteTask = new tasks.LambdaInvoke(this, "Delete", {
lambdaFunction: deleteFunction,
resultPath: "$.deleteResult",
}).addCatch(handleError, {
errors: ["States.ALL"],
resultPath: "$.deleteResult",
});

const workflow = setDeleting
.next(deleteTask)
.next(new sfn.Succeed(this, "Success"));

const stateMachine = new sfn.StateMachine(this, "DeleteWorkspace", {
definitionBody: sfn.DefinitionBody.fromChainable(workflow),
timeout: cdk.Duration.minutes(5),
comment: "Delete Workspace Workflow",
});

this.stateMachine = stateMachine;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import genai_core.workspaces
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.typing import LambdaContext

logger = Logger()


@logger.inject_lambda_context(log_event=True)
def lambda_handler(event, context: LambdaContext):
workspace_id = event["workspace_id"]
37 changes: 37 additions & 0 deletions lib/rag-engines/workspaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Construct } from "constructs";
import { SystemConfig } from "../../shared/types";
import { Shared } from "../../shared";
import { DeleteWorkspace } from "./delete-wrokspace";
import { RagDynamoDBTables } from "../rag-dynamodb-tables";
import { AuroraPgVector } from "../aurora-pgvector";
import { OpenSearchVector } from "../opensearch-vector";
import { KendraRetrieval } from "../kendra-retrieval";
import * as sfn from "aws-cdk-lib/aws-stepfunctions";

export interface WorkkspacesProps {
readonly config: SystemConfig;
readonly shared: Shared;
readonly ragDynamoDBTables: RagDynamoDBTables;
readonly auroraPgVector?: AuroraPgVector;
readonly openSearch?: OpenSearchVector;
readonly kendraRetrieval?: KendraRetrieval;
}

export class Workspaces extends Construct {
public readonly deleteWorkspaceWorkflow?: sfn.StateMachine;

constructor(scope: Construct, id: string, props: WorkkspacesProps) {
super(scope, id);

const workflow = new DeleteWorkspace(this, "DeleteWorkspace", {
config: props.config,
shared: props.shared,
ragDynamoDBTables: props.ragDynamoDBTables,
auroraPgVector: props.auroraPgVector,
openSearch: props.openSearch,
kendraRetrieval: props.kendraRetrieval,
});

this.deleteWorkspaceWorkflow = workflow.stateMachine;
}
}
26 changes: 26 additions & 0 deletions lib/shared/layers/python-sdk/python/genai_core/workspaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
CREATE_KENDRA_WORKSPACE_WORKFLOW_ARN = os.environ.get(
"CREATE_KENDRA_WORKSPACE_WORKFLOW_ARN"
)
DELETE_WORKSPACE_WORKFLOW_ARN = os.environ.get("DELETE_WORKSPACE_WORKFLOW_ARN")

WORKSPACE_OBJECT_TYPE = "workspace"

Expand Down Expand Up @@ -263,3 +264,28 @@ def create_workspace_kendra(workspace_name: str, kendra_index: dict):
return {
"id": workspace_id,
}


def delete_workspace(workspace_id: str):
response = table.get_item(
Key={"workspace_id": workspace_id, "object_type": WORKSPACE_OBJECT_TYPE}
)

item = response.get("Item")

if not item:
raise genai_core.types.CommonError("Workspace not found")

if item["status"] != "ready":
raise genai_core.types.CommonError("Workspace not ready")

response = sfn_client.start_execution(
stateMachineArn=DELETE_WORKSPACE_WORKFLOW_ARN,
input=json.dumps(
{
"workspace_id": workspace_id,
}
),
)

print(response)
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ export class WorkspacesClient extends ApiClientBase {
}
}

async deleteWorkspace(
workspaceId: string
): Promise<ApiResult<WorkspaceItem | null>> {
try {
const headers = await this.getHeaders();
const result = await fetch(this.getApiUrl(`/workspaces/${workspaceId}`), {
headers,
method: "DELETE",
});

return result.json();
} catch (error) {
return this.error(error);
}
}

async createAuroraWorkspace(params: {
name: string;
embeddingsModelProvider: string;
Expand Down
2 changes: 2 additions & 0 deletions lib/user-interface/react-app/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export abstract class Labels {
created: "success",
processing: "in-progress",
processed: "success",
deleting: "in-progress",
error: "error",
};

Expand All @@ -56,6 +57,7 @@ export abstract class Labels {
created: "Created",
processing: "Processing",
processed: "Processed",
deleting: "Deleting",
error: "Error",
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
Modal,
Box,
SpaceBetween,
Button,
Alert,
} from "@cloudscape-design/components";
import { WorkspaceItem } from "../../common/types";

export interface WorkspaceDeleteModalProps {
visible: boolean;
workspace?: WorkspaceItem;
onDelete: () => void;
onDiscard: () => void;
}

export default function WorkspaceDeleteModal(props: WorkspaceDeleteModalProps) {
return (
<Modal
visible={props.visible}
onDismiss={props.onDiscard}
header="Delete Workspace"
closeAriaLabel="Close dialog"
footer={
<Box float="right">
<SpaceBetween direction="horizontal" size="xs">
<Button variant="link" onClick={props.onDiscard}>
Cancel
</Button>
<Button
variant="primary"
onClick={props.onDelete}
data-testid="submit"
>
Delete
</Button>
</SpaceBetween>
</Box>
}
>
{props.workspace && (
<SpaceBetween size="m">
<Box variant="span">
Permanently delete workspace{" "}
<Box variant="span" fontWeight="bold">
{props.workspace.name}
</Box>
? You can't undo this action.
</Box>
<Box variant="span">Worksapce Id: {props.workspace.id}</Box>
<Alert statusIconAriaLabel="Info">
Proceeding with this action will delete the workspace with all its
content.
</Alert>
</SpaceBetween>
)}
</Modal>
);
}
Loading

0 comments on commit be5d438

Please sign in to comment.