Compare commits

...

16 commits

Author SHA1 Message Date
Alex Weininger
8b286657b0
Update bug.yaml 2021-08-18 17:41:48 -07:00
Alex Weininger
6a2fbb956b
Update bug.yaml 2021-08-18 17:41:22 -07:00
Alex Weininger
f9d42ad703
Update bug.yaml 2021-08-18 17:40:59 -07:00
Alex Weininger
e76dbc9e89
Create bug.yaml 2021-08-18 17:25:27 -07:00
alexweininger
883893aaab Fixup 2021-06-17 16:55:43 -07:00
Alex Weininger
47c405a9f5
Add setting to format json strings when viewing a document (#22)
* Add setting to format json strings when viewing a document

* Update changelog
2021-06-17 16:49:08 -07:00
alexweininger
2e77f93759 Fixup changelog 2021-06-03 07:21:04 -07:00
alexweininger
98a132fdef Update changelog 2021-06-03 07:14:15 -07:00
Alex Weininger
704bd72420
Able to set all collection properties when creating (#21)
* Able to set all collection properties when creating

* Update CHANGELOG.md
2021-06-03 07:12:21 -07:00
alexweininger
60a510b646 Update Readme 2021-05-31 01:50:55 -05:00
alexweininger
6bcbf57143 Change readme image sizes 2021-05-31 01:49:43 -05:00
alexweininger
ac466375e5 Update readme with functions feature images 2021-05-31 01:45:03 -05:00
alexweininger
bdff542ef9 update CI 2021-05-31 01:29:41 -05:00
alexweininger
38273dac92 Bump version, update changelog 2021-05-31 01:28:46 -05:00
Alex Weininger
185e1a98f4
Create function tags (#19)
* Basic functions create tag and create execution feature

* Detailed function support, create tag still broken

* create tag and pick folder

* edit changelog and bump extension version

* fix linting

* function tag creation

* Create function tags

* Fix linting
2021-05-31 01:24:51 -05:00
Alex Weininger
9da0e1ff61
Fix error when deleting user (#18) 2021-05-31 01:16:29 -05:00
46 changed files with 786 additions and 188 deletions

48
.github/ISSUE_TEMPLATE/bug.yaml vendored Normal file
View file

@ -0,0 +1,48 @@
name: Bug Report
description: File a bug report
title: "[Bug] "
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: repro
attributes:
label: Reproduction steps
description: "How do you trigger this bug? Please walk us through it step by step."
value: |
1.
2.
3.
...
render: bash
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of VS Code are you using?
options:
- Stable (Default)
- Insiders
validations:
required: true
- type: dropdown
id: os
attributes:
label: What operating system are you seeing the problem on?
multiple: true
options:
- All
- Windows
- macOS
- Linux
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell

View file

@ -26,7 +26,6 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run package
- run: npx vsce package
- uses: actions/upload-artifact@v2

View file

@ -6,6 +6,21 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
## [Unreleased]
## [0.1.3] - 2021-6-17
## Added
- New feature! JSON strings inside documents will now be automatically formatted as JSON when viewing documents. You can turn this feature off via the `appwrite.formatJsonStrings` setting.
## [0.1.2] - 2021-6-03
## Added
- Ability to set the `list`, `default` and `array` properties when creating a new collection. | [Issue #20](https://github.com/streamlux/vscode-appwrite/issues/20) | [PR #21](https://github.com/streamlux/vscode-appwrite/pull/21) | Thanks [@Maatteogekko](https://github.com/Maatteogekko)!
## [0.1.1] - 2021-5-31
## Added
- You can now easily create function tags from multiple places in the extension. [PR #19](https://github.com/streamlux/vscode-appwrite/pull/19)
## Fixed
- Fixed an error when deleting a user. [Issue #17](https://github.com/streamlux/vscode-appwrite/issues/17) [PR #18](https://github.com/streamlux/vscode-appwrite/pull/18) Thanks [@aadarshadhakalg](https://github.com/aadarshadhakalg)!
## [0.1.0] - 2021-5-29
## Functions!

View file

@ -18,7 +18,15 @@ From [appwrite.io](https://appwrite.io)
### Connect to multiple Appwrite projects
![Mutliple projects](media/features/projects/projectsView1.gif)
<img src="media/features/projects/projectsView1.gif" height="600">
### Creating function tags with ease!
![Functions feature](media/features/functions/CreateTag.gif)
### Create and manage Appwrite cloud functions, upload tags, and view function output
<img src="media/features/functions/functionsOverview.png" height="500">
### View database documents right inside VS Code.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "vscode-appwrite",
"version": "0.1.0",
"version": "0.0.9",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -2,7 +2,7 @@
"name": "vscode-appwrite",
"displayName": "Appwrite",
"description": "Manage your Appwrite resources right from VS Code!",
"version": "0.1.0",
"version": "0.1.3",
"engines": {
"vscode": "^1.55.0"
},
@ -40,151 +40,187 @@
{
"command": "vscode-appwrite.Connect",
"title": "Connect to Appwrite",
"category": ""
"category": "Appwrite"
},
{
"command": "vscode-appwrite.CreateUser",
"title": "Create user",
"icon": "$(add)"
"icon": "$(add)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.DeleteUser",
"title": "Delete user",
"icon": "$(trash)"
"icon": "$(trash)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.GetUserLogs",
"title": "Get user logs",
"icon": "$(output)"
"icon": "$(output)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.openUserInConsole",
"title": "View user in Appwrite console",
"icon": "$(link-external)"
"icon": "$(link-external)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.viewUserPrefs",
"title": "View user preferences",
"icon": "$(json)"
"icon": "$(json)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshEntry",
"title": "Refresh",
"icon": "$(refresh)"
"icon": "$(refresh)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.copyUserId",
"title": "Copy user ID",
"icon": "$(clippy)"
"icon": "$(clippy)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.copyUserEmail",
"title": "Copy user email",
"icon": "$(clippy)"
"icon": "$(clippy)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshUsersList",
"title": "Refresh users list",
"icon": "$(refresh)"
"icon": "$(refresh)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.OpenUsersDocumentation",
"title": "Open documentation",
"icon": "$(book)"
"icon": "$(book)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.OpenDatabaseDocumentation",
"title": "Open documentation",
"icon": "$(book)"
"icon": "$(book)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.viewDocumentAsJson",
"title": "View as JSON"
"title": "View as JSON",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.viewCollectionAsJson",
"title": "View collection as JSON"
"title": "View collection as JSON",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshCollection",
"title": "Refresh",
"icon": "$(refresh)"
"icon": "$(refresh)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.createRule",
"title": "Create collection rule",
"icon": "$(add)"
"icon": "$(add)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.removeRule",
"title": "Remove collection rule"
"title": "Remove collection rule",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.deleteDocument",
"title": "Delete document"
"title": "Delete document",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.deleteCollection",
"title": "Delete collection"
"title": "Delete collection",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshCollectionsList",
"title": "Refresh",
"icon": "$(refresh)"
"icon": "$(refresh)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.createCollection",
"title": "Create collection",
"icon": "$(add)"
"icon": "$(add)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.createPermission",
"title": "Create permission",
"icon": "$(add)"
"icon": "$(add)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.deletePermission",
"title": "Delete permission",
"icon": "$(trash)"
"icon": "$(trash)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.editPermission",
"title": "Edit permission",
"icon": "$(edit)"
"icon": "$(edit)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshHealth",
"title": "Refresh health",
"icon": "$(refresh)"
"icon": "$(refresh)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.openHealthDocumentation",
"title": "Open health documentation",
"icon": "$(book)"
"icon": "$(book)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshStorage",
"title": "Refresh storage",
"icon": "$(refresh)"
"icon": "$(refresh)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.openStorageDocumentation",
"title": "Open storage documentation",
"icon": "$(book)"
"icon": "$(book)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.addProject",
"title": "Add Appwrite project",
"icon": "$(plus)"
"icon": "$(plus)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.setActiveProject",
"title": "Set as active"
"title": "Set as active",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshProjects",
"title": "Refresh projects",
"icon": "$(refresh)"
"icon": "$(refresh)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshFunctions",
"title": "Refresh functions",
"icon": "$(refresh)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.refreshFunctions",
@ -194,91 +230,115 @@
{
"command": "vscode-appwrite.removeProject",
"title": "Remove project",
"icon": "$(trash)"
"icon": "$(trash)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.CreateTag",
"title": "Create function tag",
"icon": "$(cloud-upload)"
"icon": "$(cloud-upload)",
"shortTitle": "Create function tag",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.deleteTag",
"title": "Delete tag",
"icon": "$(trash)"
"icon": "$(trash)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.CreateExecution",
"title": "Execute"
"title": "Execute",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.activateTag",
"title": "Activate"
"title": "Activate",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.editValue",
"title": "Edit",
"icon": "$(edit)"
"icon": "$(edit)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.deleteFunction",
"title": "Delete",
"icon": "$(trash)"
"icon": "$(trash)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.openFunctionsDocumentation",
"title": "Open functions documentation",
"icon": "$(book)"
"icon": "$(book)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.createFunction",
"title": "Create function",
"icon": "$(add)"
"icon": "$(add)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.createFunctionVar",
"title": "Create variable",
"icon": "$(add)"
"icon": "$(add)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.deleteFunctionVar",
"title": "Delete variable",
"icon": "$(trash)"
"icon": "$(trash)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.viewExecutionOutput",
"title": "View execution stdout",
"enablement": "viewItem =~ /^((execution|execution_outputOnly))$/"
"enablement": "viewItem =~ /^((execution|execution_outputOnly))$/",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.viewExecutionErrors",
"title": "View execution stderr",
"enablement": "viewItem =~ /^((execution|execution_errorOnly))$/"
"enablement": "viewItem =~ /^((execution|execution_errorOnly))$/",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.copyExecutionOutput",
"title": "Copy execution stdout",
"enablement": "viewItem =~ /^((execution|execution_outputOnly))$/"
"enablement": "viewItem =~ /^((execution|execution_outputOnly))$/",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.copyExecutionErrors",
"title": "Copy execution stderr",
"enablement": "viewItem =~ /^((execution|execution_errorOnly))$/"
"enablement": "viewItem =~ /^((execution|execution_errorOnly))$/",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.openExecutionsInBrowser",
"title": "View executions in browser",
"enablement": "viewItem =~ /^(executions)$/",
"icon": "$(link-external)"
"icon": "$(link-external)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.openFunctionTagsInBrowser",
"title": "Open function tags in browser",
"icon": "$(link-external)"
"icon": "$(link-external)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.openFunctionSettingsInBrowser",
"title": "Open function settings in browser",
"icon": "$(link-external)"
"icon": "$(link-external)",
"category": "Appwrite"
},
{
"command": "vscode-appwrite.viewMore",
"title": "View more",
"category": "Appwrite"
}
],
"views": {
@ -570,6 +630,9 @@
}
],
"commandPalette": [
{
"command": "vscode-appwrite.CreateTag"
},
{
"command": "vscode-appwrite.Connect"
},
@ -663,6 +726,11 @@
"type": "string",
"default": "",
"markdownDescription": "Project id of the active project, see [docs](https://github.com/streamlux/vscode-appwrite/) for more information."
},
"appwrite.formatJsonStrings": {
"type": "boolean",
"default": true,
"markdownDescription": "Format JSON strings when viewing documents"
}
}
}

10
src/appwrite.d.ts vendored
View file

@ -276,7 +276,7 @@ export type CollectionsList = {
collections: Collection[];
};
export type CreatedRule = Omit<Rule, "$id" | "$collection" | "default" | "list">;
export type CreatedRule = Omit<Rule, "$id" | "$collection">;
export type Rule = {
$id: string;
@ -284,10 +284,10 @@ export type Rule = {
type: string;
key: string;
label: string;
default: string;
array: boolean;
default?: any;
required: boolean;
list: string[];
array: boolean;
list?: string[];
};
export type Permissions = {
@ -305,7 +305,7 @@ export type Client = {
setSelfSigned: (value: boolean) => void;
};
export type UsersClient = {
deleteUser: (id: string) => Promise<any>;
delete: (id: string) => Promise<any>;
deleteSessions: (id: string) => Promise<any>;
create: (email: string, password: string, name?: string) => Promise<any>;
getLogs: (id: string) => Promise<Log[]>;

View file

@ -1,10 +1,10 @@
import { Client, Execution, ExecutionList, FunctionsClient, TagList, Vars } from "../appwrite";
import { Client, Execution, ExecutionList, FunctionsClient, FunctionsList, Tag, TagList, Vars } from "../appwrite";
import { AppwriteSDK } from '../constants';
import AppwriteCall from '../utils/AppwriteCall';
import { ReadStream } from 'node:fs';
export class Functions {
private readonly functions: FunctionsClient;
public readonly functions: FunctionsClient;
constructor(client: Client) {
this.functions = new AppwriteSDK.Functions(client);
@ -13,7 +13,7 @@ export class Functions {
public async create(name: string, execute: string[], env: string, vars?: Vars, events?: string[], schedule?: string, timeout?: number): Promise<any> {
return await AppwriteCall(this.functions.create(name, execute, env, vars, events, schedule, timeout));
}
public async list(search?: string, offset?: number, limit?: number, orderType?: 'ASC' | 'DESC'): Promise<any> {
public async list(search?: string, offset?: number, limit?: number, orderType?: 'ASC' | 'DESC'): Promise<FunctionsList | undefined> {
return await AppwriteCall(this.functions.list(search, offset, limit, orderType));
}
public async get(functionId: string): Promise<any> {
@ -28,7 +28,7 @@ export class Functions {
public async delete(functionId: string): Promise<void> {
return await AppwriteCall(this.functions.delete(functionId));
}
public async createTag(functionId: string, command: string, code: ReadStream): Promise<any> {
public async createTag(functionId: string, command: string, code: ReadStream): Promise<Tag | undefined> {
return await AppwriteCall(this.functions.createTag(functionId, command, code));
}
public async listTags(id: string, search?: string, limit?: number, offset?: number, orderType?: 'ASC' | 'DESC'): Promise<TagList | undefined> {
@ -40,11 +40,11 @@ export class Functions {
public async deleteTag(functionId: string, tagId: string): Promise<void> {
return await AppwriteCall(this.functions.deleteTag(functionId, tagId));
}
public async createExecution(functionId: string, data?: string): Promise<any> {
public async createExecution(functionId: string, data?: string): Promise<Execution | undefined> {
return await AppwriteCall(this.functions.createExecution(functionId, data));
}
public async listExecutions(functionId: string, search?: string, limit?: number, offset?: number, orderType?: 'ASC' | 'DESC'): Promise<ExecutionList | undefined> {
return await AppwriteCall(this.functions.listExecutions(functionId, search, offset, limit, orderType));
return await AppwriteCall(this.functions.listExecutions(functionId, search, limit, offset, orderType));
}
public async getExecution(functionId: string, executionId: string): Promise<Execution | undefined> {
return await AppwriteCall(this.functions.getExecution(functionId, executionId));

View file

@ -16,7 +16,7 @@ export class Users {
}
public async delete(userId: string): Promise<void> {
await AppwriteCall(this.users.deleteUser(userId), () => {
await AppwriteCall(this.users.delete(userId), () => {
window.showInformationMessage(`Deleted user with id: ${userId}.`);
});
}

View file

@ -1,4 +1,4 @@
import { EditableTreeItem } from '../../tree/common/SimpleEditableTreeItem';
import { EditableTreeItem } from '../../tree/common/editable/SimpleEditableTreeItem';
export async function editValue(treeItem: EditableTreeItem): Promise<void> {
if (treeItem === undefined) {

View file

@ -0,0 +1,5 @@
import { AppwriteTreeItemBase } from '../../ui/AppwriteTreeItemBase';
export async function viewMore(treeItem: AppwriteTreeItemBase<any>): Promise<void> {
await treeItem.viewMore();
}

View file

@ -10,15 +10,13 @@ export async function createRule(rulesTreeItem: RulesTreeItem): Promise<void> {
return;
}
const ruleContext = await createRuleWizard();
const collection = rulesTreeItem.parent.collection;
const ruleContext = await createRuleWizard(collection);
if (ruleContext) {
const newRule: CreatedRule = {
...ruleContext,
type: ruleContext.type,
required: true,
array: false,
};
databaseClient.createRule(collection, newRule);

View file

@ -1,8 +1,30 @@
import { workspace } from "vscode";
import { DocumentTreeItem } from "../../tree/database/DocumentTreeItem";
import { openReadOnlyJson } from "../../ui/openReadonlyContent";
function parseJSONString(str: string): { valid: boolean; value: any } {
try {
return { value: JSON.parse(str), valid: true };
} catch (e) {
return { value: str, valid: false };
}
}
export async function viewDocumentAsJson(documentTreeItem: DocumentTreeItem): Promise<void> {
const documentId = documentTreeItem.document["$id"];
const document = documentTreeItem.document;
const documentId = document["$id"];
const formatJsonStrings = workspace.getConfiguration("appwrite").get<Boolean>("formatJsonStrings");
if (formatJsonStrings) {
Object.entries(document).forEach(([key, value]) => {
if (typeof value === "string") {
const result = parseJSONString(value);
document[key] = result.value;
}
});
}
await openReadOnlyJson(
{
label: documentId,

View file

@ -1,7 +1,8 @@
import { Tag } from '../../appwrite';
import { functionsClient } from '../../client';
import { TagTreeItem } from '../../tree/functions/tags/TagTreeItem';
export async function activateTag(tagItem: TagTreeItem): Promise<void> {
const tag = tagItem.tag;
export async function activateTag(tagItem: TagTreeItem | Tag): Promise<void> {
const tag = tagItem instanceof TagTreeItem ? tagItem.tag : tagItem;
await functionsClient?.updateTag(tag.functionId, tag.$id);
}

View file

@ -1,10 +1,57 @@
import { window } from 'vscode';
import { Execution } from '../../appwrite';
import { functionsClient } from '../../client';
import { ext } from '../../extensionVariables';
import { FunctionTreeItem } from '../../tree/functions/FunctionTreeItem';
import { sleep } from '../../utils/sleep';
import { viewExecutionErrors } from './viewExecutionErrors';
import { viewExecutionOutput } from './viewExecutionOutput';
export async function createExecution(functionTreeItem: FunctionTreeItem): Promise<void> {
const func = functionTreeItem.func;
ext.outputChannel.appendLog(`Creating execution for function ${func.name}`);
await functionsClient?.createExecution(func.$id);
await executeFunction(func.$id);
}
export async function executeFunction(functionId: string): Promise<void> {
ext.outputChannel.appendLog(`Creating execution for function with ID: ${functionId}`);
let execution = await functionsClient?.createExecution(functionId);
ext.outputChannel.appendLog(JSON.stringify(execution, null, 2));
await ext.tree?.functions?.refresh();
if (execution === undefined) {
return;
}
execution = await waitForExecution(execution);
ext.tree?.functions?.refresh();
if (execution === undefined) {
return;
}
const failed = execution.status === "failed";
const item = !failed ? "View output" : "View errors";
const action = await window.showInformationMessage(`Execution ${failed ? "failed" : "completed"} in ${execution.time.toFixed(2)}s.`, item);
if (action === item) {
if (item === "View output") {
await viewExecutionOutput(execution);
return;
}
await viewExecutionErrors(execution);
return;
}
}
async function waitForExecution(execution: Execution | undefined): Promise<Execution | undefined> {
if (execution === undefined) {
return;
}
if (execution.status === "processing" || execution.status === "waiting") {
await sleep(5000);
ext.outputChannel.appendLog("Execution still ...");
return await waitForExecution(await functionsClient?.getExecution(execution.functionId, execution.$id));
}
return execution;
}

View file

@ -1,41 +1,164 @@
import { ProgressLocation, Uri, window } from "vscode";
import { functionsClient, storageClient } from "../../client";
import { ProgressLocation, QuickPickItem, Uri, window, workspace } from "vscode";
import { functionsClient } from "../../client";
import { getTarReadStream } from "../../utils/tar";
import { ext } from "../../extensionVariables";
import * as fs from "fs";
import { TagsTreeItem } from "../../tree/functions/tags/TagsTreeItem";
import { selectWorkspaceFolder } from "../../utils/workspace";
export async function createTag(item: TagsTreeItem | Uri): Promise<void> {
import { ProgressMessage } from "../../utils/types";
import { Tag } from "../../appwrite";
import { activateTag } from "./activateTag";
export async function createTag(item?: TagsTreeItem | Uri): Promise<void> {
if (item instanceof Uri) {
window.withProgress({ location: ProgressLocation.Notification, title: "Creating tag..." }, async (_progress, _token) => {
await createTagFromUri(item);
});
const functions = await functionsClient?.list();
if (functions === undefined) {
return;
}
const pick = await window.showQuickPick(
functions.functions.map<QuickPickItem>(
(func): QuickPickItem => ({ label: func.name, description: func.env, detail: func.$id })
),
{ placeHolder: "Select a function to create tag" }
);
if (pick === undefined || pick.detail === undefined) {
return;
}
const tags = await functionsClient?.listTags(pick.detail);
let value;
if (tags && tags.tags.length > 0) {
value = tags.tags[tags.tags.length - 1].command;
}
const command = await window.showInputBox({ value, prompt: "Command to run your code" });
if (command === undefined) {
return;
}
const tag = await window.withProgress(
{ location: ProgressLocation.Notification, title: "Creating tag..." },
async (progress, _token) => {
if (pick.detail === undefined) {
return;
}
return await createTagFromUri(pick.detail, command, item, progress);
}
);
if (tag) {
await tagNotification(tag);
}
return;
}
if (item instanceof TagsTreeItem) {
const func = item.parent.func;
const folder = await selectWorkspaceFolder("Select folder of your function code.");
console.log(folder);
window.withProgress({ location: ProgressLocation.Notification, title: "Creating tag..." }, async (_progress, _token) => {
await createTagFromUri(Uri.parse(folder));
});
if (folder === undefined || folder === "") {
return;
}
const tags = await functionsClient?.listTags(func.$id);
let value;
if (tags && tags.tags.length > 0) {
value = tags.tags[tags.tags.length - 1].command;
}
const command = await window.showInputBox({ value, prompt: "Command to run your code" });
if (command === undefined) {
return;
}
const tag = await window.withProgress(
{ location: ProgressLocation.Notification, title: "Creating tag..." },
async (progress, _token) => {
return await createTagFromUri(func.$id, command, Uri.parse(folder), progress);
}
);
if (tag) {
await tagNotification(tag);
return;
}
}
if (item === undefined) {
const functions = await functionsClient?.list();
if (functions === undefined) {
return;
}
const pick = await window.showQuickPick(
functions.functions.map<QuickPickItem>(
(func): QuickPickItem => ({ label: func.name, description: func.env, detail: func.$id })
),
{ placeHolder: "Select a function to create tag" }
);
if (pick === undefined || pick.detail === undefined) {
return;
}
const funcId = pick.detail;
const folder = await selectWorkspaceFolder("Select folder of your function code.");
const tags = await functionsClient?.listTags(funcId);
let value;
if (tags && tags.tags.length > 0) {
value = tags.tags[tags.tags.length - 1].command;
}
const command = await window.showInputBox({ value, prompt: "Command to run your code" });
if (command === undefined) {
return;
}
const tag = await window.withProgress(
{ location: ProgressLocation.Notification, title: "Creating tag..." },
async (progress, _token) => {
return await createTagFromUri(funcId, command, Uri.parse(folder), progress);
}
);
if (tag) {
await tagNotification(tag);
return;
}
}
}
async function createTagFromUri(uri: Uri): Promise<void> {
const tarFilePath = await getTarReadStream(uri);
async function createTagFromUri(functionId: string, command: string, uri: Uri, progress: ProgressMessage): Promise<Tag | undefined> {
progress.report({ message: "Creating tarball", increment: 10 });
if (functionsClient === undefined) {
return;
}
if (tarFilePath === undefined) {
ext.outputChannel.appendLog("Error creating tar file.");
let tarFilePath;
try {
tarFilePath = await getTarReadStream(uri);
} catch (e) {
window.showErrorMessage("Error creating tar file.\n" + e);
return;
}
if (tarFilePath === undefined) {
window.showErrorMessage("Failed to create tar file.");
ext.outputChannel.appendLog("Failed to create tar file.");
return;
}
// somehow makes the upload work
await workspace.fs.readFile(Uri.file(tarFilePath));
progress.report({ message: "Uploading tag", increment: 60 });
try {
await functionsClient.createTag("60b1836a8e5d9", "python ./hello.py", fs.createReadStream(tarFilePath));
await storageClient?.createFile(fs.createReadStream(tarFilePath));
return await functionsClient.createTag(functionId, command, fs.createReadStream(tarFilePath));
} catch (e) {
ext.outputChannel.appendLog("Creating tag error: " + e);
}
}
async function tagNotification(tag: Tag): Promise<void> {
ext.tree?.functions?.refresh();
if (tag) {
const action = await window.showInformationMessage(
`Successfully created tag with size ${tag.size}B.`,
"Activate tag",
"View in console"
);
if (action === "Activate tag") {
await activateTag(tag);
}
if (action === "View in console") {
//
}
return;
}
}

View file

@ -1,11 +1,16 @@
import { Execution } from '../../appwrite';
import { ExecutionTreeItem } from "../../tree/functions/executions/ExecutionTreeItem";
import { openReadOnlyContent } from "../../ui/openReadonlyContent";
export async function viewExecutionErrors(executionItem: ExecutionTreeItem): Promise<void> {
export async function viewExecutionErrors(executionItem: ExecutionTreeItem | Execution): Promise<void> {
if (executionItem === undefined) {
return;
}
const execution = executionItem.execution;
await openReadOnlyContent({ label: `${executionItem.parent.parent.func.name} execution stderr`, fullId: `${execution.$id}-errors.txt` }, execution.stderr, '.txt');
let execution = executionItem as Execution;
if (executionItem instanceof ExecutionTreeItem) {
execution = executionItem.execution;
}
await openReadOnlyContent({ label: `Execution stderr`, fullId: `${execution.$id}-errors.txt` }, execution.stderr, '.txt');
}

View file

@ -1,11 +1,19 @@
import { Execution } from '../../appwrite';
import { ExecutionTreeItem } from "../../tree/functions/executions/ExecutionTreeItem";
import { openReadOnlyContent } from "../../ui/openReadonlyContent";
export async function viewExecutionOutput(executionItem: ExecutionTreeItem): Promise<void> {
export async function viewExecutionOutput(executionItem: ExecutionTreeItem | Execution): Promise<void> {
if (executionItem === undefined) {
return;
}
const execution = executionItem.execution;
await openReadOnlyContent({ label: `${executionItem.parent.parent.func.name} execution stdout`, fullId: `${execution.$id}-output.txt` }, execution.stdout, '.txt');
let execution = executionItem as Execution;
if (executionItem instanceof ExecutionTreeItem) {
execution = executionItem.execution;
}
console.log(execution.dateCreated);
await openReadOnlyContent({ label: `Execution stdout`, fullId: `${execution.$id}-output.txt` }, execution.stdout, '.txt');
}

View file

@ -42,6 +42,8 @@ import { openExecutionsInBrowser } from './functions/openExecutionsInBrowser';
import { openFunctionSettingsInBrowser } from './functions/openFunctionSettingsInBrowser';
import { openFunctionTagsInBrowser } from './functions/openFunctionTagsInBrowser';
import { viewMore } from './common/viewMore';
class CommandRegistrar {
constructor(private readonly context: ExtensionContext) {}
@ -74,6 +76,7 @@ export function registerCommands(context: ExtensionContext): void {
/** Common **/
registerCommand("editValue", editValue);
registerCommand("viewMore", viewMore);
/** General **/
registerCommand("Connect", connectAppwrite, "all");

View file

@ -20,7 +20,7 @@ export async function deleteUser(userTreeItem: UserTreeItem): Promise<void> {
);
if (shouldDeleteUser === DialogResponses.yes) {
await usersClient.delete(userId);
await usersClient.delete(userId);
refreshTree("users");
}
}

View file

@ -17,7 +17,7 @@ export type AppwriteTree = {
};
export type Ext = {
context?: ExtensionContext;
context: ExtensionContext;
outputChannel: AppwriteOutputChannel;
tree?: AppwriteTree;
};

View file

@ -0,0 +1,13 @@
import { TreeItem } from "vscode";
export abstract class EditableTreeItemBase<T> extends TreeItem {
public abstract setValue(value: T): Promise<void>;
constructor(contextValuePrefix: string, public readonly value: T, description?: string) {
super(typeof value === "string" ? value : "No label");
this.contextValue = `editable_${contextValuePrefix}`;
this.description = description ?? contextValuePrefix;
}
public abstract prompt(): Promise<void>;
}

View file

@ -0,0 +1,38 @@
import { QuickPickItem, QuickPickOptions, window } from "vscode";
import { EditableTreeItemBase } from "./EditableTreeItemBase";
export abstract class EnumEditableTreeItemBase extends EditableTreeItemBase<string[]> {
public abstract options: string[] | QuickPickItem[];
public quickPickOptions: QuickPickOptions;
constructor(contextValuePrefix: string, public readonly value: string[], description?: string) {
super(contextValuePrefix, value, description);
this.quickPickOptions = {};
}
public async prompt(): Promise<void> {
const value = await window.showQuickPick(
this.options.map<QuickPickItem>((option: QuickPickItem | string): QuickPickItem => {
if (typeof option === "string") {
return { label: option, picked: this.value.includes(option) };
}
const picked = this.value.includes(option.label);
return { ...option, picked, alwaysShow: picked };
}).sort((a, b) => {
if (a.picked) {
return -1;
}
if (b.picked) {
return 1;
}
return 0;
}),
{ ...this.quickPickOptions, canPickMany: true }
);
if (value !== undefined) {
this.setValue(value.map((item) => item.label));
}
}
}

View file

@ -0,0 +1,18 @@
import { TreeItem, window } from "vscode";
export class EditableTreeItem extends TreeItem {
public readonly setValue: (value: string) => Promise<void>;
constructor(label: string, contextValuePrefix: string, public readonly value: string, setValue: (value: string) => Promise<void>) {
super(label);
this.setValue = setValue;
this.contextValue = `editable_${contextValuePrefix}`;
}
public async prompt(): Promise<void> {
const value = await window.showInputBox({ value: this.value });
if (value !== undefined) {
this.setValue(value);
}
}
}

View file

@ -0,0 +1,22 @@
import { InputBoxOptions, window } from "vscode";
import { EditableTreeItemBase } from "./EditableTreeItemBase";
export abstract class StringEditableTreeItemBase extends EditableTreeItemBase<string> {
public abstract setValue(value: string): Promise<void>;
public inputBoxOptions: InputBoxOptions;
constructor(contextValuePrefix: string, public readonly value: string, description?: string) {
super(contextValuePrefix, value, description);
this.inputBoxOptions = {
prompt: description,
};
}
public async prompt(): Promise<void> {
const value = await window.showInputBox({ value: this.value, ...this.inputBoxOptions });
if (value !== undefined) {
this.setValue(value);
}
}
}

View file

@ -4,8 +4,8 @@ import AppwriteCall from "../../utils/AppwriteCall";
import { Collection, CollectionsList } from "../../appwrite";
import { CollectionTreeItem } from "./CollectionTreeItem";
import { AppwriteSDK } from "../../constants";
import { AppwriteTreeItemBase } from "../../ui/AppwriteTreeItemBase";
import { ext } from '../../extensionVariables';
import { AppwriteTreeItemBase } from '../../ui/AppwriteTreeItemBase';
export class DatabaseTreeItemProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
private _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | undefined | void> = new vscode.EventEmitter<
@ -34,7 +34,7 @@ export class DatabaseTreeItemProvider implements vscode.TreeDataProvider<vscode.
}
if (parent instanceof AppwriteTreeItemBase) {
return parent.getChildren?.() ?? [];
return await parent.getChildren?.() ?? [];
}
const databaseSdk = new AppwriteSDK.Database(client);

View file

@ -44,7 +44,7 @@ export class FunctionsTreeItemProvider implements vscode.TreeDataProvider<vscode
}
if (parent instanceof AppwriteTreeItemBase) {
return parent.getChildren?.() ?? [];
return await parent.getChildren?.() ?? [];
}
return [];

View file

@ -1,7 +1,5 @@
import { MarkdownString, ThemeColor, ThemeIcon, TreeItem } from "vscode";
import { Execution, ExecutionStatus } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { ext } from "../../../extensionVariables";
import { msToDate } from "../../../utils/date";
import { ExecutionsTreeItem } from "./ExecutionsTreeItem";
@ -12,17 +10,11 @@ const executionStatusIcons: Record<ExecutionStatus, ThemeIcon> = {
failed: new ThemeIcon("circle-filled", new ThemeColor("testing.iconFailed")),
};
function sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
export class ExecutionTreeItem extends TreeItem {
public isAutoRefreshing: boolean = false;
private refreshCount: number = 0;
constructor(public readonly parent: ExecutionsTreeItem, public readonly execution: Execution) {
constructor(public readonly parent: ExecutionsTreeItem, public execution: Execution) {
super(execution.$id);
this.label = this.getLabel(execution);
this.iconPath = executionStatusIcons[execution.status];
@ -31,40 +23,45 @@ export class ExecutionTreeItem extends TreeItem {
this.description = execution.trigger;
this.contextValue = this.getContextValue(execution);
this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting";
this.autoRefresh();
// if (this.isAutoRefreshing) {
// this.autoRefresh();
// }
}
async autoRefresh(): Promise<void> {
if (!this.isAutoRefreshing) {
return;
}
this.refreshCount++;
ext.outputChannel.appendLog("Refreshing execution.");
const execution = await functionsClient?.getExecution(this.parent.parent.func.$id, this.execution.$id);
// async autoRefresh(): Promise<void> {
// if (!this.isAutoRefreshing && this.refreshCount < 5) {
// return;
// }
// this.refreshCount++;
// ext.outputChannel.appendLog("Refreshing execution.");
// const execution = await functionsClient?.getExecution(this.parent.parent.func.$id, this.execution.$id);
if (!execution) {
ext.outputChannel.appendLog("Execution is undefined");
this.isAutoRefreshing = false;
return;
}
this.contextValue = this.getContextValue(execution);
this.iconPath = executionStatusIcons[execution.status];
this.label = this.getLabel(execution);
this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting";
ext.tree?.functions?.refreshChild(this);
await sleep(1000);
this.autoRefresh();
}
// if (!execution) {
// ext.outputChannel.appendLog("Execution is undefined");
// this.isAutoRefreshing = false;
// return;
// }
// this.execution = execution;
// this.contextValue = this.getContextValue(execution);
// this.iconPath = executionStatusIcons[execution.status];
// this.label = this.getLabel(execution);
// this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting";
// ext.tree?.functions?.refreshChild(this);
// await sleep(1000);
// this.autoRefresh();
// }
getLabel(execution: Execution): string {
if (execution.status === "completed" || execution.status === "failed") {
return `${this.getCreated(execution)} (${execution.time.toPrecision(2)}s)`;
return `${this.getCreated(execution)} (${this.getExecutionTime(execution)}s)`;
}
return `${this.getCreated(execution)} (${execution.status})`;
}
getExecutionTime(execution: Execution): string {
return execution.time.toPrecision(2);
}
getContextValue(execution: Execution): string {
if (execution.status === "completed" || execution.status === "failed") {
if (execution.stderr === "" && execution.stdout === "") {

View file

@ -1,21 +1,47 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { Execution, ExecutionList } from '../../../appwrite';
import { Execution, ExecutionList } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { AppwriteTreeItemBase } from '../../../ui/AppwriteTreeItemBase';
import { ExecutionTreeItem } from './ExecutionTreeItem';
import { FunctionTreeItem } from '../FunctionTreeItem';
import { ExecutionTreeItem } from "./ExecutionTreeItem";
import { FunctionTreeItem } from "../FunctionTreeItem";
import { ext } from "../../../extensionVariables";
import { AppwriteTreeItemBase } from "../../../ui/AppwriteTreeItemBase";
export class ExecutionsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> {
constructor(public readonly parent: FunctionTreeItem) {
super(parent, "Executions");
}
private executionsToShow = 10;
public async getChildren(): Promise<TreeItem[]> {
if (!functionsClient) {
return [];
}
const executions: ExecutionList | undefined = await functionsClient.listExecutions(this.parent.func.$id, undefined, undefined, undefined, 'DESC');
const children = executions?.executions.map((execution: Execution) => new ExecutionTreeItem(this, execution)) ?? [new TreeItem('No exeuctions.')];
const executions: ExecutionList | undefined = await functionsClient.listExecutions(
this.parent.func.$id,
undefined,
this.executionsToShow,
undefined,
"DESC"
);
const children = executions?.executions.map((execution: Execution) => new ExecutionTreeItem(this, execution)) ?? [
new TreeItem("No executions."),
];
if (children.length === 0) {
children.push(new TreeItem("No executions."));
}
ext.outputChannel.appendLog(`Found ${executions?.sum} executions`);
if (executions?.sum ?? (0 > this.executionsToShow && this.executionsToShow !== 100)) {
const viewMoreItem: TreeItem = {
command: {
command: "vscode-appwrite.viewMore",
arguments: [this],
title: "View more",
},
label: "View more...",
};
children.push(viewMoreItem);
}
return children;
}
@ -24,4 +50,12 @@ export class ExecutionsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> {
contextValue = "executions";
iconPath = new ThemeIcon("history");
async viewMore(): Promise<void> {
this.executionsToShow += 10;
if (this.executionsToShow > 100) {
this.executionsToShow = 100;
}
ext.tree?.functions?.refreshChild(this);
}
}

View file

@ -3,7 +3,7 @@ import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { appwriteSystemEvents } from "../../../constants";
import { ext } from "../../../extensionVariables";
import { EnumEditableTreeItemBase } from "../../common/EnumEditableTreeItem";
import { EnumEditableTreeItemBase } from "../../common/editable/EnumEditableTreeItem";
import { FunctionSettingsTreeItem } from "./FunctionSettingsTreeItem";
export class EventsTreeItem extends EnumEditableTreeItemBase {

View file

@ -1,7 +1,7 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { AppwriteTreeItemBase } from "../../../ui/AppwriteTreeItemBase";
import { AppwriteTreeItemBase } from '../../../ui/AppwriteTreeItemBase';
import { ChildTreeItem } from "../../ChildTreeItem";
import { FunctionTreeItem } from "../FunctionTreeItem";
import { EventsTreeItem } from "./EventsTreeItem";

View file

@ -2,7 +2,7 @@ import { InputBoxOptions, MarkdownString } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { ext } from "../../../extensionVariables";
import { StringEditableTreeItemBase } from '../../common/StringEditableTreeItem';
import { StringEditableTreeItemBase } from '../../common/editable/StringEditableTreeItem';
import { FunctionSettingsTreeItem } from "./FunctionSettingsTreeItem";
const tooltip = "Function name";

View file

@ -5,7 +5,7 @@ import { ext } from "../../../extensionVariables";
import cron from "cron-validate";
import { FunctionSettingsTreeItem } from "./FunctionSettingsTreeItem";
import cronstrue from "cronstrue";
import { StringEditableTreeItemBase } from '../../common/StringEditableTreeItem';
import { StringEditableTreeItemBase } from '../../common/editable/StringEditableTreeItem';
export class ScheduleTreeItem extends StringEditableTreeItemBase {
private readonly func: Function;

View file

@ -2,7 +2,7 @@ import { InputBoxOptions, MarkdownString } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { ext } from "../../../extensionVariables";
import { StringEditableTreeItemBase } from "../../common/StringEditableTreeItem";
import { StringEditableTreeItemBase } from "../../common/editable/StringEditableTreeItem";
function isNumeric(str: string) {
console.log("here");

View file

@ -2,7 +2,7 @@ import { InputBoxOptions, MarkdownString, window } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { ext } from "../../../extensionVariables";
import { StringEditableTreeItemBase } from "../../common/StringEditableTreeItem";
import { StringEditableTreeItemBase } from "../../common/editable/StringEditableTreeItem";
import { VarsTreeItem } from "./VarsTreeItem";
const tooltip = "Environment var";

View file

@ -1,6 +1,6 @@
import { TreeItem, TreeItemCollapsibleState } from "vscode";
import { Vars } from "../../../appwrite";
import { AppwriteTreeItemBase } from "../../../ui/AppwriteTreeItemBase";
import { AppwriteTreeItemBase } from '../../../ui/AppwriteTreeItemBase';
import { FunctionSettingsTreeItem } from "./FunctionSettingsTreeItem";
import { VarTreeItem } from "./VarTreeItem";

View file

@ -10,7 +10,7 @@ export class TagTreeItem extends TreeItem {
const active = func.tag === tag.$id;
this.label = `${msToDate(tag.dateCreated)}${active ? ' (Active)' : ''}`;
this.description = tag.$id;
this.iconPath = new ThemeIcon(active ? 'circle-filled' : 'circle-outline');
this.iconPath = new ThemeIcon(active ? 'circle-large-filled' : 'circle-large-outline');
this.contextValue = `tag${active ? '_active' : ''}`;
this.tooltip = new MarkdownString(`ID: ${tag.$id} \nCreated: ${msToDate(tag.dateCreated)} \nCommand: ${tag.command} \nSize: ${tag.size}B`);
}

View file

@ -14,7 +14,22 @@ export class TagsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> {
return [];
}
const tags = await functionsClient.listTags(this.parent.func.$id);
return tags?.tags.sort((a, b) => b.dateCreated - a.dateCreated).map((tag) => new TagTreeItem(this, tag)) ?? [new TreeItem('No tags.')];
const children = tags?.tags.sort((a, b) => b.dateCreated - a.dateCreated).map((tag) => new TagTreeItem(this, tag)) ?? [new TreeItem('No tags.')];
if (children.length === 0) {
const noTagsItem: TreeItem = {
command: {
command: "vscode-appwrite.CreateTag",
title: "Create tag",
arguments: [this],
tooltip: "Create a tag"
},
label: "Create a tag",
iconPath: new ThemeIcon("cloud-upload"),
};
children.push(noTagsItem);
}
return children;
}
collapsibleState = TreeItemCollapsibleState.Collapsed;

View file

@ -7,4 +7,7 @@ export abstract class AppwriteTreeItemBase<Parent = void> extends TreeItem {
abstract getChildren?(): Promise<TreeItem[]>;
viewMore(): Promise<void> {
return Promise.resolve();
}
}

View file

@ -1,42 +1,19 @@
import { QuickPickItem, window } from "vscode";
import { Collection, CollectionsList } from "../appwrite";
import { client } from "../client";
import { AppwriteSDK } from "../constants";
import AppwriteCall from "../utils/AppwriteCall";
export type CreateRuleWizardContext = {
label: string;
key: string;
type: keyof typeof ruleTypes;
default: any;
required: boolean;
array: boolean;
list?: string[];
};
export async function createRuleWizard(): Promise<CreateRuleWizardContext | undefined> {
const label = await window.showInputBox({
placeHolder: "Label",
prompt: "Attribute internal display name",
});
if (label === undefined) {
return;
}
const key = await window.showInputBox({
placeHolder: "Key",
prompt: "Attribute key name. Used as the document JSON key in the Database API.",
});
if (key === undefined) {
return;
}
const ruleTypeItems: QuickPickItem[] = Object.entries(ruleTypes).map(([label, description]) => ({
label,
description,
}));
const type = await window.showQuickPick(ruleTypeItems);
if (type === undefined) {
return;
}
if (label && key && type) {
return { label, key, type: (type.label as unknown) as keyof typeof ruleTypes };
}
return undefined;
}
const ruleTypes = {
text: "Any string value.",
numeric: "Any integer or float value.",
@ -45,6 +22,119 @@ const ruleTypes = {
url: "Any valid URL.",
email: "Any valid email address.",
ip: "Any valid IP v4 or v6 address.",
document:
"Accept a valid child document. When using this type you are also required to pass the 'list' parameter with an array of the collections UID values of the document types you want to accept.",
document: "Accept a valid child document from specified collection(s).",
};
type RuleType = keyof typeof ruleTypes;
export async function createRuleWizard(collection: Collection): Promise<CreateRuleWizardContext | undefined> {
const label = await window.showInputBox({
placeHolder: "Attribute label",
prompt: "Attribute internal display name",
validateInput: (value) => {
if (value === "") {
return "Label cannot be empty.";
}
},
});
if (label === undefined) {
return;
}
const key = await window.showInputBox({
placeHolder: "Attribute key name",
prompt: "Attribute key name. Used as the document JSON key in the Database API.",
ignoreFocusOut: true,
validateInput: (value) => {
if (value === "") {
return "Key name cannot be empty.";
}
},
});
if (key === undefined) {
return;
}
const ruleTypeItems: QuickPickItem[] = Object.entries(ruleTypes).map(([label, description]) => ({
label,
detail: description,
}));
const typeItem = await window.showQuickPick(ruleTypeItems, { placeHolder: "Rule value type." });
const type: RuleType | undefined = (typeItem?.label as RuleType) ?? undefined;
if (typeItem === undefined || type === undefined) {
return;
}
let list: string[] | undefined = undefined;
if (type === "document") {
const databaseSdk = new AppwriteSDK.Database(client);
const collectionsList = await AppwriteCall<CollectionsList, CollectionsList>(databaseSdk.listCollections());
if (collectionsList === undefined) {
window.showErrorMessage("Could not get collections list.");
return;
}
if (collectionsList) {
const collections = collectionsList.collections.filter((c) => c.$id !== collection.$id);
const qpItems: QuickPickItem[] = collections.map((collection) => ({
label: collection.name,
description: collection.$id,
}));
const listInput = await window.showQuickPick(qpItems, {
canPickMany: true,
placeHolder: "Collections which contain valid child documents for this document attribute.",
ignoreFocusOut: true,
});
list = listInput?.map((item) => item.description as string) ?? [];
}
}
if (label === "document" && list === undefined) {
return;
}
const array = await window.showQuickPick(["Primitive", "Array"], {
placeHolder: "Decide if this rule is a primitive or an array of values.",
ignoreFocusOut: true,
});
if (array === undefined) {
return;
}
const required = await window.showQuickPick(["Required", "Optional"], {
placeHolder: "Decide if this rule value is required in order to pass document validation.",
ignoreFocusOut: true,
});
if (required === undefined) {
return;
}
const defaultValue = await window.showInputBox({
placeHolder: "Default value (press Enter to skip)",
prompt: "Default value for this rule type. Make sure that the default value is able to pass validation in order to avoid errors when skipping optional values.",
ignoreFocusOut: true,
});
if (defaultValue === undefined) {
return;
}
if (label && key && type) {
return {
label,
key,
type,
default: defaultValue,
array: array === "Array",
required: required === "Required",
list,
};
}
return undefined;
}

View file

@ -1,5 +1,9 @@
import dayjs = require('dayjs');
import dayjs = require("dayjs");
import utc = require("dayjs/plugin/utc");
import timezone = require("dayjs/plugin/timezone"); // dependent on utc plugin
dayjs.extend(utc);
dayjs.extend(timezone);
export function msToDate(ms: number): string {
return dayjs(ms).format("LTS");
return dayjs(ms * 1000).tz(dayjs.tz.guess()).format("LTS");
}

5
src/utils/sleep.ts Normal file
View file

@ -0,0 +1,5 @@
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}

View file

@ -2,8 +2,8 @@ import tar = require("tar");
import { Uri, window, workspace } from "vscode";
import { ext } from "../extensionVariables";
import * as path from "path";
import * as os from "os";
import * as fs from "fs";
import { sleep } from './sleep';
export async function getTarReadStream(folder: Uri): Promise<string | undefined> {
try {
@ -15,11 +15,14 @@ export async function getTarReadStream(folder: Uri): Promise<string | undefined>
window.showErrorMessage("No workspace open.");
return;
}
ext.outputChannel.appendLog(`Creating '${tarName}' in '${workspace.workspaceFolders?.[0].uri.fsPath}'...`);
const tarFilePath = path.join(ext.context?.globalStorageUri.fsPath ?? '', tarName);
await workspace.fs.createDirectory(ext.context.globalStorageUri);
const tarFilePath = path.join(os.tmpdir(), tarName);
tar.create({ gzip: true, cwd: cwd, mode: 1777 }, [path.relative(cwd, folder.fsPath)]).pipe(fs.createWriteStream(tarFilePath));
tar.create({ gzip: true, cwd: cwd }, [path.relative(cwd, folder.fsPath)]).pipe(fs.createWriteStream(tarFilePath));
await sleep(500);
ext.outputChannel.appendLog(`Created ${tarFilePath}`);

6
src/utils/types.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
import { Progress } from 'vscode';
export type ProgressMessage = Progress<{
message?: string | undefined;
increment?: number | undefined;
}>;