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

feat: filter resources on explorer #139

Merged
merged 4 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fast-planes-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"amplify-backend-vscode": minor
---

feat: add resource filters on resource explorer
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,42 @@ AWS Amplify Backend VSCode let you following features.

## Features

### Resource Explorer

The AWS Backend Explorer gives you a view of the AWS resources in Amplify Sandbox environment that you can work with when using the AWS Backend Explorer.You can open the AWS Resource page of your choice in the AWS Console of your browser.

![Amplify Backend Explorer](images/explorer.gif)

#### Filter resources

You can filter resources in the AWS Resource Explorer.
You can then switch which filter to use with the filter switching action.
In addition, in `settings.json` you can define custom filters with a pair of names and an array of AWS resources in the tree, as shown below.

```json
{
"amplifyBackend.explorerFilters": [
{
"name": "simple",
"resources": [
"AWS::AppSync::GraphQLApi",
"Custom::AmplifyDynamoDBTable",
"AWS::Lambda::Function",
"AWS::S3::Bucket"
]
}
]
}
```

#### Switch AWS Profile

You can switch AWS Profile to explor the AWS resources.

![Switch AWS Profile](images/switch_profile.gif)

### Secret in sandbox environment

You can view/add/edit/remove secrets in your sandbox environment.

![Secrets Explorer](images/secrets_explorer.gif)
Expand Down
37 changes: 37 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
"dark": "resources/dark/refresh.svg"
}
},
{
"command": "amplify-backend-explorer.switchFilter",
"title": "Switch Filter",
"icon": "$(filter)"
},
{
"command": "amplify-backend-explorer.openConsole",
"title": "Open AWS Console"
Expand Down Expand Up @@ -67,6 +72,11 @@
"when": "view == amplify-backend-explorer",
"group": "navigation"
},
{
"command": "amplify-backend-explorer.switchFilter",
"when": "view == amplify-backend-explorer",
"group": "navigation"
},
{
"command": "amplify-backend-secrets-explorer.refresh",
"when": "view == amplify-backend-secrets-explorer",
Expand Down Expand Up @@ -108,6 +118,33 @@
"name": "Amplify Backend Secrets Explorer"
}
]
},
"configuration": {
"title": "Amplify Backend",
"properties": {
"amplifyBackend.explorerFilters": {
"type": "array",
"default": [],
"description": "List of filters to apply to the Amplify Backend Explorer",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Name of filter to apply to the Amplify Backend Explorer"
},
"resources": {
"type": "array",
"description": "AWS resource types shown in the Amplify Backend Explorer",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"repository": {
Expand Down
4 changes: 4 additions & 0 deletions src/console-url-builder/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { StackResource } from "@aws-sdk/client-cloudformation";

export function isSupportedResourceType(resourceType: string) {
return urlBuilders[resourceType] !== undefined;
}

export function buildUrl(
stackResource: Pick<
StackResource,
Expand Down
19 changes: 16 additions & 3 deletions src/explorer/amplify-backend-tree-data-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { AmplifyBackendBaseNode } from "./amplify-backend-base-node";
import { isStackNode } from "./utils";
import { detectAmplifyProjects } from "./amplify-project-detector";
import { AmplifyProject, getAmplifyProject } from "../project";
import {
DefaultResourceFilterProvider,
ResourceFilterProvider,
} from "./resource-filter";

export class AmplifyBackendTreeDataProvider
implements vscode.TreeDataProvider<AmplifyBackendBaseNode>
Expand All @@ -23,7 +27,10 @@ export class AmplifyBackendTreeDataProvider
AmplifyBackendBaseNode | undefined | void
> = this._onDidChangeTreeData.event;

constructor(private workspaceRoot: string) {}
constructor(
private workspaceRoot: string,
private resourceFilterProvider: ResourceFilterProvider
) {}

refresh() {
this._onDidChangeTreeData.fire();
Expand Down Expand Up @@ -56,7 +63,8 @@ export class AmplifyBackendTreeDataProvider
if (!response.StackResources) {
return [];
}
return response.StackResources.map((resource) => {
const predicate = this.resourceFilterProvider.getResourceFilterPredicate();
return response.StackResources.filter(predicate).map((resource) => {
return new AmplifyBackendResourceTreeNode(
resource.LogicalResourceId!,
resource.ResourceType!,
Expand All @@ -75,7 +83,12 @@ export class AmplifyBackendTreeDataProvider

const children: AmplifyBackendBaseNode[] = [];
const profile = Auth.instance.getProfile();
children.push(new AuthNode(`Connected with profile: ${profile}`, profile));
const filterName = this.resourceFilterProvider.getResourceFilterName();
const label =
filterName === DefaultResourceFilterProvider.NONE_FILTER_NAME
? `Connected with profile: ${profile}`
: `Connected with profile ${profile} and resources filtered with ${filterName}`;
children.push(new AuthNode(label, profile));
if (nodes.length) {
children.push(...nodes);
} else {
Expand Down
87 changes: 87 additions & 0 deletions src/explorer/resource-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as vscode from "vscode";
import { StackResource } from "@aws-sdk/client-cloudformation";
import { isSupportedResourceType } from "../console-url-builder";

export type ResourceFilter = {
name: string;
resources: string[];
};

export const getResourceFilters = () => {
const config = vscode.workspace.getConfiguration();
return config.get<ResourceFilter[]>("amplifyBackend.explorerFilters") ?? [];
};

export type ResourceFilterPredicate = (resource: StackResource) => boolean;

const stackPredicate = (resource: StackResource) =>
"AWS::CloudFormation::Stack" === resource.ResourceType;

const defaultPredicate = (resource: StackResource) =>
isSupportedResourceType(resource.ResourceType!);

const or =
(predicates: ResourceFilterPredicate[]) => (resource: StackResource) =>
predicates.some((predicate) => predicate(resource));

const all = () => true;

export interface ResourceFilterProvider {
getResourceFilterName(): string;
setResourceFilterName(filterName: string): Thenable<void>;
getResourceFilterPredicate(): ResourceFilterPredicate;
}

export class DefaultResourceFilterProvider implements ResourceFilterProvider {
static readonly NONE_FILTER_NAME = "none";
private static readonly PRESET_FILTER_NAME = "default";
private static readonly KEY_FILTER_NAME = "filterName";

private readonly state: vscode.Memento;

constructor(state: vscode.Memento) {
this.state = state;
}

getResourceFilterName(): string {
return this.state.get(
DefaultResourceFilterProvider.KEY_FILTER_NAME,
DefaultResourceFilterProvider.NONE_FILTER_NAME
);
}

setResourceFilterName(filterName: string): Thenable<void> {
return this.state.update(
DefaultResourceFilterProvider.KEY_FILTER_NAME,
filterName
);
}

getResourceFilterPredicate(): ResourceFilterPredicate {
const filters = getResourceFilters();
const filterName = this.getResourceFilterName();
if (filterName === DefaultResourceFilterProvider.NONE_FILTER_NAME) {
return all;
}
if (filterName === DefaultResourceFilterProvider.PRESET_FILTER_NAME) {
return or([stackPredicate, defaultPredicate]);
}
const filter = filters.find((filter) => filter.name === filterName);
if (!filter) {
return all;
}
return or([
stackPredicate,
(resource: StackResource) =>
filter.resources.includes(resource.ResourceType!),
]);
}

getResourceFilterNames(): string[] {
return [
DefaultResourceFilterProvider.NONE_FILTER_NAME,
DefaultResourceFilterProvider.PRESET_FILTER_NAME,
...getResourceFilters().map((filter) => filter.name),
];
}
}
28 changes: 27 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SecretsTreeDataProvider } from "./secrets/secrets-tree-data-provider";
import { editSecretCommand } from "./secrets/edit-secret-command";
import { removeSecretCommand } from "./secrets/remove-secret-command";
import { addSecretCommand } from "./secrets/add-secret-command";
import { DefaultResourceFilterProvider } from "./explorer/resource-filter";

export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
Expand Down Expand Up @@ -34,8 +35,13 @@ export function activate(context: vscode.ExtensionContext) {
? vscode.workspace.workspaceFolders[0].uri.fsPath
: undefined;

const resourceFilterProvider = new DefaultResourceFilterProvider(
context.workspaceState
);

const amplifyBackendTreeDataProvider = new AmplifyBackendTreeDataProvider(
rootPath || ""
rootPath || "",
resourceFilterProvider
);
vscode.window.createTreeView("amplify-backend-explorer", {
treeDataProvider: amplifyBackendTreeDataProvider,
Expand Down Expand Up @@ -103,6 +109,26 @@ export function activate(context: vscode.ExtensionContext) {
}
)
);

context.subscriptions.push(
vscode.commands.registerCommand(
"amplify-backend-explorer.switchFilter",
async () => {
const names = resourceFilterProvider.getResourceFilterNames();
const quickPick = vscode.window.createQuickPick();
quickPick.items = names.map((label) => ({ label }));
quickPick.onDidChangeSelection((selection) => {
if (selection[0]) {
resourceFilterProvider.setResourceFilterName(selection[0].label);
quickPick.hide();
amplifyBackendTreeDataProvider.refresh();
}
});
quickPick.onDidHide(() => quickPick.dispose());
quickPick.show();
}
)
);
}

export function deactivate() {}
Loading