22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/connection-manager/css/app.css:
--------------------------------------------------------------------------------
1 | .vscode-dark input[data-v-e7f38064]{color:#ddd}.vscode-light input[data-v-e7f38064]{color:#444}.vscode-dark button[data-v-e7f38064]{background-color:#333;color:#ddd}.vscode-light button[data-v-e7f38064]{background-color:#ddd;color:#333}.connections[data-v-e7f38064]{width:100%}.connections td[data-v-e7f38064],.connections th[data-v-e7f38064]{text-align:left;border-bottom:1px solid #444}.connections td[data-v-e7f38064]{padding:5px 0}.connections th[data-v-e7f38064]{padding:5px 7px}.connections input[data-v-e7f38064]{display:block;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box;padding:10px 5px;text-overflow:ellipsis;border:0;border-left:2px solid transparent;outline:0;background:transparent}.connections input[data-v-e7f38064]:focus{border-left:2px solid blue}button[data-v-e7f38064]{margin-right:10px;border:1px solid #ddd;border-radius:3px;cursor:pointer}button.delete[data-v-e7f38064]{background-color:darkred;color:#d3d3d3!important}button[data-v-e7f38064]:disabled{cursor:not-allowed;color:#999}
--------------------------------------------------------------------------------
/images/run.icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/syntaxes/language-configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "comments": {
3 | // symbol used for single line comment. Remove this entry if your language does not support line comments
4 | // "lineComment": "//",
5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments
6 | "blockComment": [ "/*", "*/" ]
7 | },
8 | // symbols used as brackets
9 | "brackets": [
10 | ["{", "}"],
11 | ["%%[", "]%%"],
12 | ["%%=", "=%%"],
13 | //["[", "]"],
14 | ["(", ")"]
15 | ],
16 | // symbols that are auto closed when typing
17 | "autoClosingPairs": [
18 | ["{", "}"],
19 | ["%%[", "]%%"],
20 | ["%%=", "=%%"],
21 | //["[", "]"],
22 | ["(", ")"],
23 | ["\"", "\""],
24 | ["'", "'"]
25 | ],
26 | // symbols that that can be used to surround a selection
27 | "surroundingPairs": [
28 | ["{", "}"],
29 | ["%%[", "]%%"],
30 | ["%%=", "=%%"],
31 | //["[", "]"],
32 | ["(", ")"],
33 | ["\"", "\""],
34 | ["'", "'"]
35 | ]
36 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sergey Agadzhanov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to the "AMPScript" extension will be documented in this file.
3 |
4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
5 |
6 | ## [3.0.6] - 2022-11-15
7 | - Support for Shared Dataextensions (only when connected to ENT BU)
8 |
9 | ## [3.0.5] - 2022-09-28
10 | - Switched to native HTTP calls to support Corporate Proxies
11 |
12 | ## [3.0.4] - 2022-06-24
13 | - Edit Automation Studio Script Activities (special thanks go to @danielrubiorueda)
14 | - Fix AXIOS concurrency requests issue
15 |
16 | ## [3.0.2] - 2021-04-08
17 | - Clean Build
18 |
19 | ## [3.0.1] - 2021-03-23
20 | ### Added
21 | - MCFS: SQL Queries
22 | - MCFS: Dataextensions
23 |
24 | ## [2.0.2] - 2020-09-08
25 | ### Added
26 | - MCFS: fix incorrect path names on Windows
27 |
28 | ## [2.0.1] - 2020-08-30
29 | ### Added
30 | - MCFS functionality (ability to connect directly to MC)
31 | - Hover function snippets
32 |
33 | ## [1.4.0] - 2020-02-25
34 | ### Added
35 | - Code snippets for all functions and some language elements
36 |
37 | ## [1.3.0] - 2020-02-19
38 | ### Changed
39 | - Migration of the grammar to a JSON format
40 | - Revision of the grammar
41 | ### Added
42 | - Highlighting in HTML attributes
43 |
44 | ## [1.2.3] - 2020-01-29
45 | ### Added
46 | - Support for Ampscript in HTML comments
47 | - Highlighting for a few missig functions
48 | - Other small changes
49 |
50 | ## [1.1.0] - 2017-07-11
51 | ### Added
52 | - HTML support
53 |
54 | ## [1.0.0] - 2017-07-10
55 | ### Changed
56 | - String escape symbols
57 |
58 | ## [Unreleased]
59 | - Initial release
--------------------------------------------------------------------------------
/images/filter.icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
21 |
--------------------------------------------------------------------------------
/src/connection-manager/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
76 |
77 |
79 |
--------------------------------------------------------------------------------
/src/libs/folderManagerUri.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import * as path from 'path';
3 | import { FolderController } from './folderController';
4 |
5 | export class FolderManagerUri {
6 | public readonly connectionId: string;
7 | public readonly name: string;
8 | public readonly globalPath: string;
9 | public readonly mountPath: string;
10 | public readonly localPath: string;
11 | public readonly mountFolderName: string;
12 | public readonly type: vscode.FileType;
13 | public readonly isAsset: boolean;
14 | private readonly uri: vscode.Uri;
15 |
16 | constructor(uri: vscode.Uri) {
17 | this.uri = uri;
18 | this.connectionId = uri.authority;
19 | this.name = path.basename(uri.path);
20 | this.type = FolderController.getInstance().hasFileExtension(path.extname(this.name)) ? vscode.FileType.File : vscode.FileType.Directory;
21 | this.isAsset = this.name.startsWith('Ω');
22 | const basePath = `${uri.scheme}://${this.connectionId}/`;
23 |
24 | const chunks = uri.path.replace(/\\/g, '/').replace(/(^\/)|(\/$)/g, '').split('/');
25 | this.mountFolderName = chunks.shift() || '';
26 | this.localPath = chunks.join('/');
27 | this.mountPath = this.mountFolderName !== '' ? basePath + this.mountFolderName + '/' : '';
28 | this.globalPath = this.mountPath + this.localPath;
29 | }
30 |
31 | getChildPath(childDirectoryName: string) {
32 | return this.globalPath + (this.globalPath.endsWith('/') ? '' : '/') + childDirectoryName;
33 | }
34 |
35 | get parent(): FolderManagerUri | undefined {
36 | const chunks = this.localPath.split('/');
37 |
38 | if (chunks.length > 0) {
39 | chunks.pop();
40 | return new FolderManagerUri(vscode.Uri.parse(this.mountPath + chunks.join('/')));
41 | }
42 |
43 | return undefined;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/webscraper/function-convert.py:
--------------------------------------------------------------------------------
1 | import json
2 | from pyquery import PyQuery
3 | import re
4 |
5 | def readParams(html, syntax):
6 | result = {}
7 | result["description"] = ""
8 | result["body"] = [syntax]
9 |
10 | if not html:
11 | return result
12 |
13 | desc = ""
14 |
15 | pq = PyQuery(html)
16 |
17 | for row in pq('tbody tr').items():
18 | r = PyQuery(row)
19 | index = r('td:first').text().strip()
20 | type = r('td:nth-child(2)').text().strip()
21 | required = r('td:nth-child(3)').text().lower().strip()
22 | description = r('td:nth-child(4)').text().strip()
23 |
24 | desc = desc + "" + index + (" " if not required else "*") + " [" + type.upper() + "] \n" + description + "\n\n"
25 |
26 | if index.isdigit():
27 | syntax = re.sub("([\(\,]\s*)" + index + "(\s*[\,\)])", "\\1${" + index + ":" + type.upper() + "}\\2", syntax)
28 | else:
29 | print("Check " + syntax + " - " + index)
30 |
31 | result["description"] = desc.strip()
32 | result["body"] = [syntax]
33 | return result
34 |
35 |
36 |
37 |
38 |
39 | result = {}
40 |
41 | with open('functions.json') as json_file:
42 | data = json.load(json_file)
43 | for line in data:
44 | params = readParams(line['params'], line['syntax'])
45 |
46 | result[line['link'].lower()] = {
47 | "prefix": line['link'],
48 | "body": params['body'],
49 | "description": (line['link'] if not line['syntax'] else line['syntax'])
50 | + "\n\n" + line['desc']
51 | + ("" if not params["description"] else "\n\n========== PARAMETERS ============\n\n" + params["description"])
52 | + ("" if not line["example"] else "\n\n========== EXAMPLES ==============\n\n" + line["example"])
53 | + "\n"
54 | }
55 |
56 | with open('function-snippets.json', 'w') as outfile:
57 | json.dump(result, outfile)
--------------------------------------------------------------------------------
/src/libs/asset.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export type LazyContentDelegate = (() => Promise) | undefined;
4 |
5 | export class AssetFile {
6 | name: string;
7 | content: string;
8 | path: string;
9 | private getLazyContent: LazyContentDelegate;
10 |
11 | constructor(name: string, content: string, path: string, getLazyContent: LazyContentDelegate = undefined) {
12 | this.name = name;
13 | this.content = content;
14 | this.path = path;
15 | this.getLazyContent = getLazyContent;
16 | }
17 |
18 | write(data: Uint8Array): void {
19 | this.content = new TextDecoder("utf-8").decode(data);
20 | }
21 |
22 | async read(): Promise {
23 | if (this.content === '' && this.getLazyContent !== undefined) {
24 | this.content = await this.getLazyContent();
25 | }
26 |
27 | return new TextEncoder().encode(this.content);
28 | }
29 | }
30 |
31 | export class Asset {
32 | name: string;
33 | directoryName: string;
34 | content: string;
35 | readonly connectionId: string;
36 | private _files: Array;
37 |
38 | constructor(name: string, directoryName: string, content: string, connectionId: string, files: Array | undefined) {
39 | this.name = name;
40 | this.directoryName = directoryName;
41 | this.content = content;
42 | this.connectionId = connectionId;
43 | this._files = new Array();
44 |
45 | if (files !== undefined) {
46 | this._files.push(...files);
47 | }
48 | }
49 |
50 | get files(): Array {
51 | return [
52 | new AssetFile('__raw.readonly.json', this.content, ''),
53 | ...this._files
54 | ];
55 | }
56 |
57 | getFile(name: string): AssetFile {
58 | const f = this.files.find(f => f.name == name);
59 |
60 | if (f !== undefined) return f;
61 |
62 | throw new Error(`File ${name} not found`);
63 | }
64 | }
--------------------------------------------------------------------------------
/src/libs/httpUtils.ts:
--------------------------------------------------------------------------------
1 | import { URL } from 'url';
2 | import { Buffer } from 'buffer';
3 | import * as https from 'https';
4 | import { ClientRequest, IncomingMessage, RequestOptions } from 'http';
5 | import * as zlib from 'zlib';
6 |
7 | export class ApiRequestConfig {
8 | public method = '';
9 | public url = '';
10 | public data = '';
11 | public params: any = null;
12 | public baseURL = '';
13 | public headers: any = {};
14 |
15 | public constructor(config: any){
16 | this.method = config.method;
17 | this.url = config.url;
18 | this.data = config.data;
19 | this.params = config.params;
20 | this.baseURL = config.baseURL;
21 | this.headers = config.headers || {};
22 | }
23 |
24 | public getURL(): URL {
25 | const url = new URL(this.url, this.baseURL);
26 |
27 | if(this.params){
28 | const searchParams = new URLSearchParams(this.params);
29 | url.search = searchParams.toString();
30 | }
31 |
32 | return url;
33 | }
34 | }
35 |
36 | export class HttpUtils {
37 | private static instance: HttpUtils | null = null;
38 |
39 | static getInstance(): HttpUtils {
40 | if (HttpUtils.instance === null) {
41 | HttpUtils.instance = new HttpUtils();
42 | }
43 |
44 | return HttpUtils.instance;
45 | }
46 |
47 | makeRestApiCall(config: ApiRequestConfig): Promise{
48 | config.headers['Content-Type'] = 'application/json';
49 | return this.makeApiCall(config).then(data => JSON.parse(data));
50 | }
51 |
52 | makeApiCall(config: ApiRequestConfig): Promise{
53 | const options: RequestOptions = {
54 | method: config.method,
55 | headers: config.headers
56 | };
57 |
58 | return new Promise((resolve: any, reject: any) => {
59 | const req: ClientRequest = https.request(config.getURL().toString(), options, (res: IncomingMessage) => {
60 | const chunks: Array = [];
61 |
62 | res.on('data', (data: Buffer) => {
63 | chunks.push(data);
64 | });
65 |
66 | res.on('end', () => {
67 | const buffer = Buffer.concat(chunks);
68 |
69 | if(res.headers['content-encoding'] == 'gzip'){
70 | zlib.gunzip(buffer, (err, output: Buffer) => {
71 | if(!err && res?.statusCode && res.statusCode >= 200 && res.statusCode < 300){
72 | resolve(output.toString());
73 | }
74 | else{
75 | reject(err);
76 | }
77 | });
78 | }
79 | else{
80 | const output = buffer.toString();
81 | if(res?.statusCode && res.statusCode >= 200 && res.statusCode < 300){
82 | resolve(output);
83 | }
84 | else{
85 | reject(output);
86 | }
87 | }
88 | });
89 | });
90 |
91 | req.on('error', (err) => {
92 | reject(err);
93 | });
94 |
95 | if (config.data) {
96 | const data = typeof config.data === 'object' ? JSON.stringify(config.data) : config.data;
97 | req.write(data);
98 | }
99 |
100 | req.end();
101 | });
102 | }
103 | }
--------------------------------------------------------------------------------
/src/mcfsFileSystemProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { FolderController } from './libs/folderController';
3 | import { FolderManagerUri } from './libs/folderManagerUri';
4 | import { Utils } from './libs/utils';
5 |
6 | export class MCFS implements vscode.FileSystemProvider {
7 | stat(uri: vscode.Uri): vscode.FileStat {
8 | const fmUri = new FolderManagerUri(uri);
9 |
10 | if (!FolderController.getInstance().hasManager(fmUri)) {
11 | throw vscode.FileSystemError.FileNotFound();
12 | }
13 |
14 | return {
15 | type: fmUri.type,
16 | mtime: Date.now(),
17 | size: 0,
18 | ctime: 0,
19 | };
20 | }
21 |
22 | async readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
23 | const fmUri = new FolderManagerUri(uri);
24 |
25 | if (!FolderController.getInstance().hasManager(fmUri)) {
26 | throw vscode.FileSystemError.FileNotFound();
27 | }
28 |
29 | const pSubdirectories = FolderController.getInstance().getSubdirectories(fmUri);
30 | const pAssets = FolderController.getInstance().getAssets(fmUri);
31 | const pFiles = FolderController.getInstance().getFiles(fmUri);
32 |
33 | try {
34 | const [subdirectories, assets, files] = await Promise.all([pSubdirectories, pAssets, pFiles]);
35 |
36 | return [
37 | ...subdirectories.map<[string, vscode.FileType]>(d => [d, vscode.FileType.Directory]),
38 | ...assets.map<[string, vscode.FileType]>(a => [a.directoryName, vscode.FileType.Directory]),
39 | ...files.map<[string, vscode.FileType]>(f => [f.name, vscode.FileType.File])
40 | ];
41 | }
42 | catch (err) {
43 | Utils.getInstance().showErrorMessage(err);
44 | throw err;
45 | }
46 |
47 | }
48 |
49 | async readFile(uri: vscode.Uri): Promise {
50 | const fmUri = new FolderManagerUri(uri);
51 |
52 | if (!FolderController.getInstance().hasManager(fmUri)) {
53 | throw vscode.FileSystemError.FileNotFound();
54 | }
55 |
56 | try {
57 | return FolderController.getInstance().readFile(fmUri);
58 | }
59 | catch (err) {
60 | Utils.getInstance().showErrorMessage(err);
61 | throw err;
62 | }
63 | }
64 |
65 | async writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): Promise {
66 | const fmUri = new FolderManagerUri(uri);
67 |
68 | if (!FolderController.getInstance().hasManager(fmUri)) {
69 | throw vscode.FileSystemError.FileNotFound();
70 | }
71 |
72 | try {
73 | return FolderController.getInstance().writeFile(fmUri, content);
74 | }
75 | catch (err) {
76 | Utils.getInstance().logError(err);
77 | throw new Error(Utils.getInstance().getErrorMessage(err));
78 | }
79 |
80 | }
81 |
82 | /* NOT IMPLEMENTED */
83 |
84 | createDirectory(uri: vscode.Uri): void {
85 | throw new Error("CreateDirectory not implemented yet");
86 | }
87 |
88 | rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void {
89 | throw new Error("Rename not implemented yet");
90 | }
91 |
92 | delete(uri: vscode.Uri): void {
93 | throw new Error("Delete not implemented yet");
94 | }
95 |
96 | private _emitter = new vscode.EventEmitter();
97 | private _bufferedEvents: vscode.FileChangeEvent[] = [];
98 | private _fireSoonHandle?: NodeJS.Timer;
99 |
100 | readonly onDidChangeFile: vscode.Event = this._emitter.event;
101 |
102 | watch(_resource: vscode.Uri): vscode.Disposable {
103 | // ignore, fires for all changes...
104 | return new vscode.Disposable(() => { });
105 | }
106 |
107 | private _fireSoon(...events: vscode.FileChangeEvent[]): void {
108 | this._bufferedEvents.push(...events);
109 |
110 | if (this._fireSoonHandle) {
111 | clearTimeout(this._fireSoonHandle);
112 | }
113 |
114 | this._fireSoonHandle = setTimeout(() => {
115 | this._emitter.fire(this._bufferedEvents);
116 | this._bufferedEvents.length = 0;
117 | }, 5);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/libs/folderManager.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { Asset, AssetFile } from './asset';
4 | import { FolderManagerUri } from './folderManagerUri';
5 |
6 | export interface Directory {
7 | id: number;
8 | parentId: number | undefined;
9 | name: string;
10 | }
11 |
12 | export interface CustomAction {
13 | command: string;
14 | waitLabel: string;
15 | callback: (fmUri: FolderManagerUri, content: string) => Promise;
16 | }
17 |
18 | export abstract class FolderManager {
19 | readonly mountFolderName: string;
20 | protected assetsCache: Map;
21 |
22 | public customActions: Array;
23 |
24 | constructor() {
25 | this.mountFolderName = this.constructor.name;
26 | this.assetsCache = new Map();
27 | this.customActions = [];
28 | }
29 |
30 | /**
31 | * Returns a list of subdirectories in the provided directory uri
32 | * @param directoryUri
33 | */
34 | abstract getSubdirectories(directoryUri: FolderManagerUri): Promise>;
35 |
36 | /**
37 | * Returns a list of assets in the provided directory uri. Should save all retrived assets to the assetsCache property
38 | * @param directoryUri
39 | */
40 | abstract getAssetsInDirectory(directoryUri: FolderManagerUri): Promise>;
41 |
42 | /**
43 | * Saves the content of the file back to the asset object
44 | * @param asset
45 | * @param file
46 | */
47 | abstract setAssetFile(asset: Asset, file: AssetFile): Promise;
48 |
49 | /**
50 | * Returns an asset based on the provided uri
51 | * @param assetUri - uri of an asset to return
52 | * @param forceRefresh - if TRUE ignores cache and reloads an asset from the backend
53 | */
54 | async getAsset(assetUri: FolderManagerUri, forceRefresh?: boolean): Promise {
55 | const directoryUri = assetUri.parent;
56 |
57 | let asset = this.assetsCache.get(assetUri.globalPath);
58 |
59 | // If assets have not been loaded yet, or a refresh was requested
60 | if (directoryUri !== undefined && (asset === undefined || forceRefresh === true)) {
61 | await this.getAssetsInDirectory(directoryUri);
62 | asset = this.assetsCache.get(assetUri.globalPath);
63 | }
64 |
65 | if (asset !== undefined) {
66 | return asset;
67 | }
68 |
69 | throw new Error(`Asset ${assetUri.globalPath} not found`);
70 | }
71 |
72 | /**
73 | * Save asset back to the backend
74 | * @param asset
75 | */
76 | abstract saveAsset(asset: Asset): Promise;
77 |
78 | /**
79 | * Returns a list of files, extracted from an asset
80 | * @param assetUri
81 | */
82 | async getAssetFiles(assetUri: FolderManagerUri): Promise> {
83 | const asset: Asset = await this.getAsset(assetUri, false);
84 | return asset?.files || [];
85 | }
86 |
87 | /**
88 | * Each asset is represented as a folder. This function returns the name of the asset directory based on the original asset name.
89 | * DIRECTORY NAMES SHOULD ALWAYS START WITH THE Ω SYMBOL FOLLOWED BY A SPACE.
90 | * This is required because VSCode sorts the content of
91 | * each directory by name. Ω at the beginning allows us to push all assets to the end of the list after all folders.
92 | * Ω at the beginning of the asset folder name is also used to distinguish between regular folder and asset folders
93 | * @param name - original name of the asset
94 | * @param assetData - raw asset data
95 | */
96 | getAssetDirectoryName(name: string, assetData: any): string {
97 | return `Ω 🟦 ${name}.${this.constructor.name.toLowerCase()}`;
98 | }
99 |
100 | /**
101 | * Returns the original name of the asset based on asset directory name
102 | * @param directoryName
103 | */
104 | getAssetNameByDirectoryName(directoryName: string): string | undefined {
105 | /*eslint no-control-regex: "off"*/
106 | const match = directoryName.match(/^(?([^\x00-\x7F]|\s)+)(?.+)\.(?[^.]+)$/);
107 | return match?.groups?.name;
108 | }
109 |
110 | /**
111 | * Returns a list of file extension added by the Manager. File extensions are used by the FolderController to distinguish between folders and files.
112 | * Each element in the array should be lowercase and should start with the '.' symbol
113 | * Example: return ['.amp', '.sql', '.json']
114 | */
115 | getFileExtensions(): Array {
116 | return [];
117 | }
118 | }
--------------------------------------------------------------------------------
/connection-manager/index.html:
--------------------------------------------------------------------------------
1 |
MCFS Connection manager
This extensions can connect Visual Studio Code directly to your Salesforce Marketing Cloud Account. This allows you to easily change content in MC without leaving the text editor and helps you to save time and avoid frequent copy-pasting. It also helps you to better control the content of your emails, content blocks and cloud pages by exposing additional content attributes that are not available in the UI of MC; it allows you to use "Super Content" in almost all supported content types.
In your MC accout, create a new installed package and add a 'Server-to-Server' API integration Component
Add the following permissions:
CHANNELS: Email (Read and Write)
CHANNELS: Web (Read, Write, Publish)
ASSETS: Saved Content (Read and Write)
AUTOMATION: Automations (Read, Write, Execute)
DATA: Data Extensions (Read, Write)
Grant access to all required BUs
Provide package details in the connection manager below, save it and connect
You'll find the entire Content Builder library in your File Explorer tab
To open Connection Manager next time press 'CMD+Shift+P' (Mac) or 'CTRL+Shift+P' (Windows) and start typing 'MCFS'. Then hit Enter
How to connect to Marketing Cloud
Step #1: Go to Setup
Log in to Marketing Cloud, hover over your user name in the top right corner and click on "Setup"
Step #2: Open "Installed packages"
In the left-hand panel of the setup section go to "Platform tools -> Apps -> Installed packages"
Step #3: Create new Package
Click "NEW" in the top right corner to create a new Package
Step #4: Add API integration to the Package
Click "Add Component", select "API Integration", then click "Next"
Step #5: Select "Server-to-server" type
... and click "Next"
Step #6: Select required permissions
CHANNELS: Email (Read and Write), CHANNELS: Web (Read, Write and Publish), ASSETS: Saved Content (Read and Write), AUTOMATION: Automations (Read, Write, Execute), DATA: Data Extensions (Read, Write)
Step #7: Go to Access tab
... and click "Enable All Business Units" to enable your Package for the entire MC Accout
Step #8: Enable Package for BUs
... or enable Package only for some Business Units
Step #9: Create a new Connection in VS Code
Copy "Authentication Base URI", "Client Id", "Client Secret" and your Business Unit ID (MID) to the corresponding fields of connection manager and click "Save changes"
This extensions can connect Visual Studio Code directly to your Salesforce Marketing Cloud Account. This
53 | allows you to easily change content in MC without leaving the text editor and helps you to
54 | save time and avoid frequent copy-pasting. It also helps you to better control the content of your
55 | emails, content blocks and cloud pages by exposing additional content attributes that are not available in
56 | the UI of MC; it allows you to use "Super Content" in almost all supported content types.
57 |
58 |
59 |
In your MC accout, create a new installed package and add a 'Server-to-Server' API integration Component
60 |
61 |
Add the following permissions:
62 |
63 |
CHANNELS: Email (Read and Write)
64 |
CHANNELS: Web (Read, Write, Publish)
65 |
ASSETS: Saved Content (Read and Write)
66 |
AUTOMATION: Automations (Read, Write, Execute)
67 |
DATA: Data Extensions (Read, Write)
68 |
69 |
70 |
Grant access to all required BUs
71 |
Provide package details in the connection manager below, save it and connect
72 |
You'll find the entire Content Builder library in your File Explorer tab
73 |
To open Connection Manager next time press 'CMD+Shift+P' (Mac) or 'CTRL+Shift+P' (Windows) and start
74 | typing 'MCFS'. Then hit Enter
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
How to connect to Marketing Cloud
83 |
84 |
85 |
86 |
87 |
Step #1: Go to Setup
88 |
Log in to Marketing Cloud, hover over your user name in the top right corner and click on
89 | "Setup"
90 |
91 |
92 |
93 |
Step #2: Open "Installed packages"
94 |
In the left-hand panel of the setup section go to "Platform tools -> Apps -> Installed
95 | packages"
96 |
97 |
98 |
99 |
Step #3: Create new Package
100 |
Click "NEW" in the top right corner to create a new Package
CHANNELS: Email (Read and Write), CHANNELS: Web (Read, Write and Publish), ASSETS: Saved Content (Read and Write), AUTOMATION: Automations (Read, Write, Execute), DATA: Data Extensions (Read, Write)
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
Step #7: Go to Access tab
134 |
... and click "Enable All Business Units" to enable your Package for the entire MC Accout
135 |
136 |
137 |
Step #8: Enable Package for BUs
138 |
... or enable Package only for some Business Units
139 |
140 |
141 |
Step #9: Create a new Connection in VS Code
142 |
Copy "Authentication Base URI", "Client Id", "Client Secret" and your Business Unit ID (MID)
143 | to the corresponding fields of connection manager and click "Save changes"
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/connection-manager/js/app.js:
--------------------------------------------------------------------------------
1 | (function(){"use strict";var t={762:function(t,n,e){var o=e(144),i=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",{attrs:{id:"app"}},[e("ConnectionList",{attrs:{connections:t.connections},on:{connect:function(n){return t.connect(n)},save:function(n){return t.save(n)}}}),e("Help")],1)},c=[],s=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",{staticClass:"hello"},[e("h2",[t._v("Connections list")]),e("div",{staticStyle:{"margin-bottom":"10px"}},[e("button",{on:{click:function(n){return t.add()}}},[t._v("NEW CONNECTION")]),e("button",{on:{click:function(n){return t.save()}}},[t._v("SAVE CHANGES")])]),e("table",{staticClass:"connections",attrs:{cellpadding:"0",cellspacing:"0",border:"0"}},[t._m(0),e("tbody",t._l(this.localConnections,(function(n,o){return e("tr",{key:o},[e("td",[e("button",{ref:"btn_connect",refInFor:!0,on:{click:function(e){return t.connect(n,o)}}},[t._v("CONNECT")])]),e("td",[e("input",{directives:[{name:"model",rawName:"v-model",value:n.name,expression:"c.name"}],attrs:{type:"text",placeholder:"connection name"},domProps:{value:n.name},on:{change:function(n){t.hasChanges=!0},input:function(e){e.target.composing||t.$set(n,"name",e.target.value)}}})]),e("td",[e("input",{directives:[{name:"model",rawName:"v-model",value:n.account_id,expression:"c.account_id"}],attrs:{type:"text",placeholder:"business unit id"},domProps:{value:n.account_id},on:{change:function(n){t.hasChanges=!0},input:function(e){e.target.composing||t.$set(n,"account_id",e.target.value)}}})]),e("td",[e("input",{directives:[{name:"model",rawName:"v-model",value:n.authBaseUri,expression:"c.authBaseUri"}],attrs:{type:"text",placeholder:"api auth base uri"},domProps:{value:n.authBaseUri},on:{change:function(n){t.hasChanges=!0},input:function(e){e.target.composing||t.$set(n,"authBaseUri",e.target.value)}}})]),e("td",[e("input",{directives:[{name:"model",rawName:"v-model",value:n.client_id,expression:"c.client_id"}],attrs:{type:"password",placeholder:"client id"},domProps:{value:n.client_id},on:{change:function(n){t.hasChanges=!0},input:function(e){e.target.composing||t.$set(n,"client_id",e.target.value)}}})]),e("td",[e("input",{directives:[{name:"model",rawName:"v-model",value:n.client_secret,expression:"c.client_secret"}],attrs:{type:"password",placeholder:"client secret"},domProps:{value:n.client_secret},on:{change:function(n){t.hasChanges=!0},input:function(e){e.target.composing||t.$set(n,"client_secret",e.target.value)}}})]),e("td",[e("button",{staticClass:"delete",on:{click:function(n){return t.remove(o)}}},[t._v("✕")])])])})),0)])])},a=[function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("thead",[e("tr",[e("th",{attrs:{width:"100"}}),e("th",{attrs:{width:"100"}},[t._v("Label")]),e("th",{attrs:{width:"100"}},[t._v("MID")]),e("th",[t._v("Auth Base Uri")]),e("th",{attrs:{width:"200"}},[t._v("Client ID")]),e("th",{attrs:{width:"200"}},[t._v("Client Secret")]),e("th",{attrs:{width:"50§"}})])])}],r={name:"connectionList",props:{connections:Array},data:function(){return{localConnections:[],hasChanges:!1}},methods:{add:function(){this.hasChanges=!0,this.localConnections.push({name:"my connection "+(this.localConnections.length+1),account_id:"",authBaseUri:"",client_id:"",client_secret:""})},remove:function(t){this.localConnections.splice(t,1)},connect:function(t,n){this.$refs.btn_connect&&this.$refs.btn_connect.length>n&&(this.$refs.btn_connect[n].setAttribute("disabled","true"),this.$refs.btn_connect[n].innerHTML="CONNECTED"),this.hasChanges&&this.save(),setTimeout((()=>{this.$emit("connect",t)}),100)},save:function(){this.hasChanges=!1,this.$emit("save",this.localConnections)}},watch:{connections:function(t){this.localConnections=t.slice(0)}},components:{}},u=r,l=e(1),d=(0,l.Z)(u,s,a,!1,null,"e7f38064",null),h=d.exports,f={name:"app",data:function(){return{vscode:null,connections:[]}},methods:{save:function(t){this.connections=t,this.vscode.postMessage({action:"UPDATE",content:t})},connect:function(t){this.vscode.postMessage({action:"CONNECT",content:t})},onMessageReceived:function(t){switch(t.data.action){case"SET_CONFIGS":this.connections=t.data.content;break;default:break}}},mounted:function(){"function"===typeof acquireVsCodeApi?this.vscode=acquireVsCodeApi():this.vscode={postMessage:t=>{console.log("postMessage",t)}},this.vscode.postMessage({action:"SEND_CONFIGS"}),window.addEventListener("message",(t=>{this.onMessageReceived(t)}))},components:{ConnectionList:h}},p=f,v=(0,l.Z)(p,i,c,!1,null,null,null),m=v.exports;o.Z.config.productionTip=!1,new o.Z({render:t=>t(m)}).$mount("#app")}},n={};function e(o){var i=n[o];if(void 0!==i)return i.exports;var c=n[o]={exports:{}};return t[o](c,c.exports,e),c.exports}e.m=t,function(){var t=[];e.O=function(n,o,i,c){if(!o){var s=1/0;for(l=0;l=c)&&Object.keys(e.O).every((function(t){return e.O[t](o[r])}))?o.splice(r--,1):(a=!1,c0&&t[l-1][2]>c;l--)t[l]=t[l-1];t[l]=[o,i,c]}}(),function(){e.d=function(t,n){for(var o in n)e.o(n,o)&&!e.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:n[o]})}}(),function(){e.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"===typeof window)return window}}()}(),function(){e.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)}}(),function(){var t={143:0};e.O.j=function(n){return 0===t[n]};var n=function(n,o){var i,c,s=o[0],a=o[1],r=o[2],u=0;if(s.some((function(n){return 0!==t[n]}))){for(i in a)e.o(a,i)&&(e.m[i]=a[i]);if(r)var l=r(e)}for(n&&n(o);un&&(this.$refs.btn_connect[n].setAttribute("disabled","true"),this.$refs.btn_connect[n].innerHTML="CONNECTED"),this.hasChanges&&this.save(),setTimeout((function(){e.$emit("connect",t)}),100)},save:function(){this.hasChanges=!1,this.$emit("save",this.localConnections)}},watch:{connections:function(t){this.localConnections=t.slice(0)}},components:{}}),u=r,l=e(1001),d=(0,l.Z)(u,s,a,!1,null,"e7f38064",null),h=d.exports,f={name:"app",data:function(){return{vscode:null,connections:[]}},methods:{save:function(t){this.connections=t,this.vscode.postMessage({action:"UPDATE",content:t})},connect:function(t){this.vscode.postMessage({action:"CONNECT",content:t})},onMessageReceived:function(t){switch(t.data.action){case"SET_CONFIGS":this.connections=t.data.content;break;default:break}}},mounted:function(){var t=this;"function"===typeof acquireVsCodeApi?this.vscode=acquireVsCodeApi():this.vscode={postMessage:function(t){console.log("postMessage",t)}},this.vscode.postMessage({action:"SEND_CONFIGS"}),window.addEventListener("message",(function(n){t.onMessageReceived(n)}))},components:{ConnectionList:h}},p=f,v=(0,l.Z)(p,i,c,!1,null,null,null),m=v.exports;o.Z.config.productionTip=!1,new o.Z({render:function(t){return t(m)}}).$mount("#app")}},n={};function e(o){var i=n[o];if(void 0!==i)return i.exports;var c=n[o]={exports:{}};return t[o](c,c.exports,e),c.exports}e.m=t,function(){var t=[];e.O=function(n,o,i,c){if(!o){var s=1/0;for(l=0;l=c)&&Object.keys(e.O).every((function(t){return e.O[t](o[r])}))?o.splice(r--,1):(a=!1,c0&&t[l-1][2]>c;l--)t[l]=t[l-1];t[l]=[o,i,c]}}(),function(){e.d=function(t,n){for(var o in n)e.o(n,o)&&!e.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:n[o]})}}(),function(){e.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"===typeof window)return window}}()}(),function(){e.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)}}(),function(){var t={143:0};e.O.j=function(n){return 0===t[n]};var n=function(n,o){var i,c,s=o[0],a=o[1],r=o[2],u=0;if(s.some((function(n){return 0!==t[n]}))){for(i in a)e.o(a,i)&&(e.m[i]=a[i]);if(r)var l=r(e)}for(n&&n(o);u = [];
14 |
15 | public static readonly extensionId = "sergey-agadzhanov.AMPscript";
16 | public static get extensionVersion(): string {
17 | return vscode.extensions.getExtension(Utils.extensionId)?.packageJSON?.version || "";
18 | }
19 |
20 |
21 | static getInstance(): Utils {
22 | if (Utils.instance === null) {
23 | Utils.instance = new Utils();
24 | }
25 |
26 | return Utils.instance;
27 | }
28 |
29 | constructor() {
30 | this.channel = vscode.window.createOutputChannel('MCFS');
31 | this.telemetry = new TelemetryReporter(
32 | "mcfs",
33 | Utils.extensionVersion,
34 | Buffer.from("OTc1M2Y5OTAtOTY0Yy00M2Q2LWFiYTEtYjZiMmQyZmVlZDNi", "base64").toString("utf-8")
35 | );
36 | }
37 |
38 | sendTelemetryEvent(event: string, deduplicate = false, isError = false): void {
39 | try{
40 | if (deduplicate) {
41 | if (this.telementryEventLog.includes(event)) return;
42 | else this.telementryEventLog.push(event);
43 | }
44 |
45 | if (isError) this.telemetry.sendTelemetryErrorEvent(event);
46 | else this.telemetry.sendTelemetryEvent(event);
47 | }
48 | catch(err: any){
49 | this.logError(err)
50 | }
51 | }
52 |
53 | showInformationMessage(message: string): void {
54 | this.log(message)
55 | vscode.window.showInformationMessage(message);
56 | }
57 |
58 | showErrorMessage(err: any, isModal = false): void {
59 | const message = this.getErrorMessage(err);
60 | this.logError(err);
61 | vscode.window.showErrorMessage(message, {
62 | modal: isModal
63 | } as vscode.MessageOptions);
64 | }
65 |
66 | getErrorMessage(err: any): string {
67 | let message = '';
68 |
69 | if (typeof err === 'string') {
70 | message = err;
71 | }
72 |
73 | message += err?.message ? err?.message + '. ' : '';
74 | message += err?.details ? err?.details + '. ' : '';
75 | message += err?.data ? err.data : '';
76 |
77 | return message;
78 | }
79 |
80 | log(message: string): void {
81 | this.channel.appendLine(`${new Date().toISOString()} => ${message}`);
82 | }
83 |
84 | logError(err: any): void {
85 | const message: string = this.getErrorMessage(err);
86 |
87 | this.log('ERROR ********************************************');
88 | this.log(message);
89 | this.log(JSON.stringify(err, null, 2));
90 | this.log('**************************************************');
91 |
92 | }
93 |
94 | readJSON(path: string): Promise {
95 | return new Promise((resolve, reject) => {
96 | fs.readFile(require.resolve(path), (err, data) => {
97 | if (err) {
98 | reject(err)
99 | }
100 | else {
101 | resolve(JSON.parse(data.toString('utf-8')));
102 | }
103 | })
104 | });
105 | }
106 |
107 | getConfig(section: string): any {
108 | const config = vscode.workspace.getConfiguration('mcfs');
109 | return config?.get(section);
110 | }
111 |
112 | setConfig(section: string, value: any): void {
113 | const config = vscode.workspace.getConfiguration('mcfs');
114 |
115 | const updateInterval = setInterval(_ => {
116 | if (this.isConfigUpdated) {
117 | this.isConfigUpdated = false;
118 | config?.update(section, value, true).then(_ => {
119 | this.isConfigUpdated = true;
120 | clearInterval(updateInterval);
121 | });
122 | }
123 | }, 100);
124 | }
125 |
126 | setConfigField(section: string, field: string, value: any): void {
127 | const data = this.getConfig(section);
128 | data[field] = value;
129 | this.setConfig(section, data);
130 | }
131 |
132 | delay(time: number): any {
133 | return new Promise((resolve) => {
134 | setTimeout(() => resolve(time), time);
135 | });
136 | }
137 | }
138 |
139 |
140 |
141 | export class WebPanelMessage {
142 | public action = '';
143 | public content: any = null;
144 | }
145 |
146 | export class WebPanel {
147 | private panel: vscode.WebviewPanel | undefined;
148 | private id: string;
149 | private title: string;
150 |
151 | public onMessageReceived: (message: any) => void;
152 |
153 | constructor(id: string, title: string) {
154 | this.id = id;
155 | this.title = title;
156 | this.onMessageReceived = () => null;
157 | }
158 |
159 | public open(webviewPath: string): void {
160 | const webviewPathUri = vscode.Uri.file(webviewPath);
161 | const indexPath = path.join(webviewPathUri.fsPath, 'index.html');
162 |
163 | this.panel = vscode.window.createWebviewPanel(
164 | this.id,
165 | this.title,
166 | vscode.ViewColumn.One,
167 | {
168 | enableScripts: true,
169 | localResourceRoots: [webviewPathUri]
170 | }
171 | );
172 |
173 | this.panel?.webview.onDidReceiveMessage((message: any) => {
174 | this.onMessageReceived(message);
175 | });
176 |
177 | let content = '';
178 |
179 | fs.readFile(indexPath, (err, data) => {
180 | if (err) {
181 | Utils.getInstance().logError(err);
182 | content = `Error: unable to open confirguration manager using path ${indexPath}. ${err.toString()}`;
183 | return;
184 | }
185 | else {
186 | content = data
187 | .toString()
188 | .replace(/\/(css|js|assets|img)\//g, `${this.panel?.webview.asWebviewUri(webviewPathUri)}/$1/`);
189 | }
190 |
191 | if (this.panel) {
192 | this.panel.webview.html = content;
193 | }
194 | });
195 | }
196 |
197 | public close(): void {
198 | this.panel?.dispose();
199 | }
200 |
201 | public postMessage(message: WebPanelMessage): void {
202 | this.panel?.webview.postMessage(message);
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/libs/folderManagers/scripts.ts:
--------------------------------------------------------------------------------
1 | import { Asset, AssetFile } from '../asset';
2 | import { FolderManagerUri } from '../folderManagerUri';
3 | import { FolderManager, Directory } from '../folderManager';
4 | import { ConnectionController } from '../connectionController';
5 | import { Utils } from '../utils';
6 | import { ApiRequestConfig } from '../httpUtils';
7 |
8 |
9 | export class ScriptsFolderManager extends FolderManager {
10 | readonly mountFolderName: string = "Scripts";
11 | private directoriesCache: Map>>;
12 |
13 | constructor() {
14 | super();
15 | this.directoriesCache = new Map>>();
16 | }
17 |
18 | /* Interface implementation */
19 |
20 | async getAssetsInDirectory(directoryUri: FolderManagerUri): Promise {
21 | const directoryId: number = await this.getDirectoryId(directoryUri);
22 |
23 | const hasTokenScopes = await ConnectionController.getInstance().hasTokenRequiredScopes(
24 | directoryUri.connectionId,
25 | ['automations_execute', 'automations_read', 'automations_write']
26 | );
27 |
28 | if (!hasTokenScopes) {
29 | Utils.getInstance().sendTelemetryEvent("error.manager-automations.missing_api_scope", true, true);
30 | throw new Error('Additional permissions are required for this function: AUTOMATION: Automations (Read, Write, Execute). Please update your installed package and restart VSCode');
31 | }
32 |
33 | const config = new ApiRequestConfig({
34 | method: 'get',
35 | url: `/automation/v1/scripts/category/${directoryId}`,
36 | params: {
37 | '$page': 1,
38 | '$pageSize': 100,
39 | 'retrievalType': 1
40 | }
41 | });
42 |
43 | const data: any = await ConnectionController.getInstance().restRequest(directoryUri.connectionId, config);
44 |
45 | const assets: Array = new Array();
46 |
47 | (data.items as Array).forEach(a => {
48 | const asset: Asset = new Asset(
49 | a.name || '???',
50 | //AssetSubtype.SQL,
51 | this.getAssetDirectoryName(a.name, a),
52 | JSON.stringify(a, null, 2),
53 | directoryUri.connectionId,
54 | this.extractFiles(a)
55 | );
56 | //this.assetsCache.set(directoryUri.getChildPath(asset.fsName), asset);
57 | this.assetsCache.set(directoryUri.getChildPath(asset.directoryName), asset);
58 | assets.push(asset);
59 | });
60 |
61 | return assets;
62 | }
63 |
64 | async getSubdirectories(directoryUri: FolderManagerUri): Promise {
65 | const directoryId: number = await this.getDirectoryId(directoryUri);
66 |
67 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(directoryUri, directoryId);
68 | return subdirectories.map(d => d.name);
69 | }
70 |
71 | async saveAsset(asset: Asset): Promise {
72 | const assetData: any = JSON.parse(asset.content);
73 |
74 | const config = new ApiRequestConfig({
75 | method: 'patch',
76 | url: `/automation/v1/scripts/${assetData.ssjsActivityId}`,
77 | data: assetData
78 | });
79 |
80 | await ConnectionController.getInstance().restRequest(asset.connectionId, config);
81 | }
82 |
83 | async setAssetFile(asset: Asset, file: AssetFile): Promise {
84 | const assetData: any = JSON.parse(asset.content);
85 | assetData[file.path] = file.content;
86 | asset.content = JSON.stringify(assetData, null, 2);
87 | }
88 |
89 | getAssetDirectoryName(name: string, assetData: any): string {
90 | return `Ω 🟪 ${name}.script`;
91 | }
92 |
93 | getFileExtensions(): Array {
94 | return ['.js', '.json'];
95 | }
96 |
97 | private extractFiles(assetData: any): Array {
98 | const result: Array = [];
99 |
100 | if (assetData?.script !== undefined) {
101 | result.push(new AssetFile(
102 | "script.js",
103 | assetData?.script,
104 | "script"
105 | ));
106 | }
107 |
108 | return result;
109 | }
110 |
111 | private async getAllDirectories(connectionId: string): Promise> {
112 | const cached = this.directoriesCache.get(connectionId);
113 |
114 | if (cached !== undefined) {
115 | return cached;
116 | }
117 |
118 | const config = new ApiRequestConfig({
119 | method: 'get',
120 | url: '/automation/v1/folders/',
121 | params: {
122 | '$pagesize': '200',
123 | '$filter': `categorytype eq ssjsactivity`
124 | }
125 | });
126 |
127 | const pDirectories = ConnectionController.getInstance()
128 | .restRequest(connectionId, config)
129 | .then(response => {
130 | const directories = new Array();
131 |
132 | response?.items?.forEach((e: any) => {
133 | directories.push({
134 | id: e.categoryId,
135 | parentId: e.parentId,
136 | name: e.name
137 | } as Directory);
138 | });
139 |
140 | return directories;
141 | });
142 |
143 | this.directoriesCache.set(connectionId, pDirectories);
144 |
145 | return pDirectories;
146 | }
147 |
148 | private async getSubdirectoriesByDirectoryId(uri: FolderManagerUri, directoryId: number): Promise> {
149 | const allDirectories = await this.getAllDirectories(uri.connectionId);
150 |
151 | return allDirectories.filter(d => d.parentId === directoryId);
152 | }
153 |
154 | private async getDirectoryId(uri: FolderManagerUri): Promise {
155 | const allDirectories = await this.getAllDirectories(uri.connectionId);
156 |
157 | if (uri.localPath === '') {
158 | const root: Directory | undefined = allDirectories.find(d => d.parentId === 0);
159 | if (root !== undefined) {
160 | return root.id;
161 | }
162 | }
163 |
164 | if (uri.parent !== undefined) {
165 | const parentDirectoryId = await this.getDirectoryId(uri.parent);
166 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(uri, parentDirectoryId);
167 |
168 | for (const d of subdirectories) {
169 | if (d.name === uri.name) {
170 | return d.id;
171 | }
172 | }
173 | }
174 |
175 | throw new Error(`Path not found: ${uri.globalPath}`);
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/PROMO.md:
--------------------------------------------------------------------------------
1 | # MCFS [AMPScript] v3.0.6
2 |
3 | Greetings Marketing Cloud Experts! You've just updated (or installed) an **AMPscript [MCFS]** extension for Visual Studio Code. This version brings some really cool new features, that I would like to share with you. Share your ideas using [this form](https://docs.google.com/forms/d/e/1FAIpQLSc8NCJcqTxMIIJ5J1pWKTnPY2JewvTS8GU6b9-Lvhdze1N4RA/viewform?usp=sf_link), leave your feedback on the [Extension Page](https://marketplace.visualstudio.com/items?itemName=sergey-agadzhanov.AMPscript) or add a star on my [github repository](https://github.com/Bizcuit/vscode-ampscript).
4 |
5 | ```diff
6 | + === NEW FEATURES ===
7 |
8 | + Extension now works with Corporate Proxies
9 | + Support for shared Dataextensions (you need to be connected to Ent BU)
10 | ```
11 |
12 | ### To run an SQL query
13 | * Connect to your MC account
14 | * Find your SQL Query asset in the "SQL Queries" folder and open a "query.sql" file
15 | * Click a "Run SQL Query" button located in the top right corner of the editor (or run a "MCFS: Run SQL Query" command from the Command Pallet)
16 |
17 | 
18 |
19 |
20 | ### To filter a Dataextension
21 | * Connect to your MC account
22 | * Find your Dataextension asset in the "Dataextensions" folder and open a "rows.csv" file
23 | * Click a "Filter a Dataextension" button located in the top right corner of the editor (or run a "MCFS: Filter a Dataextension" command from the Command Pallet)
24 | * Set the filter and hit enter
25 | * Filter example: OrderID = 'ORD2123F2' AND SubscriberKey = 'ABC'
26 |
27 | 
28 |
29 |
30 | ## SFMC DevTools published
31 |
32 | We have been talking about this behind the scenes already for quite some time but on March 26 the [SFMC DevTools](https://bit.ly/mc-devtools) were finally open-sourced. It allows you to up-/download all kinds of metadata, run mass-deployments to multiple BUs and on top it can be integrated into your IDE or CI/CD solution. And here comes the best part: We are looking into the possibility of integrating it into this VSCode extension.
33 |
34 |
35 | # Direct connection to Marketing Cloud
36 |
37 | ## 1. Connect directly to your Marketing Cloud Account
38 |
39 | With a quick 5 minutes setup you'll be able to edit content blocks, emails, cloudpages, dataextensions and SQL queries without leaving Visual Studio Code. You can now avoid frequent copy-pasting and focus on your work. Have a look a quick demo below. To open Connection Manager:
40 | * Press F1 (or 'CMD+Shift+P' on Mac and 'CTRL+Shift+P' on Windows)
41 | * Start typing 'MCFS'
42 | * Find 'MCFS Connecton Manager' and then press Enter
43 | * You'll find detailed setup instuctions there
44 |
45 | 
46 |
47 | ## 1.a How to connect to Marketing Cloud
48 |
49 | As of now, you **can only edit existing assets** (content blocks, emails, cloudpage and json message). Functionality that is not supported at the moment: create new asset, rename asset, move asset to a different folder, delete asset.
50 |
51 | * In your MC accout, create a new installed package and add a 'Server-to-Server' API integration Component
52 | * Add the following permissions:
53 | * CHANNELS: Email (Read and Write)
54 | * CHANNELS: Web (Read, Write, Publish)
55 | * ASSETS: Saved Content (Read and Write)
56 | * AUTOMATION: Automations (Read, Write, Execute)
57 | * DATA: Data Extensions (Read, Write)
58 | * Grant access to all required BUs
59 | * Provide package details in the connection manager below, save it and connect
60 | * You'll find the entire Content Builder library in your File Explorer tab
61 | * To open Connection Manager next time press F1 (or 'CMD+Shift+P' on Mac and 'CTRL+Shift+P' on Windows) and start typing 'MCFS'. Find 'MCFS Connecton Manager' and then hit Enter
62 |
63 | Detailed instructions with screenshots are available directly in the Connection Manager. To open Connection Manager press F1 (or 'CMD+Shift+P' on Mac and 'CTRL+Shift+P' on Windows) and start typing 'MCFS'. Find 'MCFS Connecton Manager' and then hit Enter.
64 |
65 | ### Assets that you can work with
66 | * Content Builder assets (Emails, Messages and Content Blocks)
67 | * Landing Pages (created with Content Builder editor)
68 | * Dataextensions (Edit data in dataextensions, apply filters, export to CSV etc.)
69 | * SQL Queries (Edit queries and Run them)
70 |
71 | ### 1.b How to edit assets directly from Visual Studio Code
72 |
73 | Each asset is presented as a folder that starts with an 'Ω' symbol. You can easily distinguish different asset types based on the colored square that goes after 'Ω':
74 | * 🟥 - blocks
75 | * 🟦 - emails
76 | * 🟨 - templates
77 | * 🟩 - cloudpages
78 | * 🟪 - mobile messages
79 |
80 | Each asset folder includes a readonly '__raw.readonly.json' file. This is an API representation of the asset. You can not modify. Instead you can modify all other files available under the asset folder. Each file represents a specific part of the asset. For the template based email you will see for example smth like:
81 | * _htmlcontent.amp - template used to create an email
82 | * _subject.amp - subject line of the email
83 | * _preheader.amp - preheader of the email
84 | * s01.b01.content.amp - content of the first content block (b01) that is located in the first template stack/placeholder (s01)
85 | * s01.b01.super.amp - super content of the block above. Learn more about super content [here](https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/design_super_content.htm)
86 | * s01.b02.content.amp - content of the second block in the first template stack
87 | * s01.b02.super.amp - super content of the second block in the first template stack
88 | * s02.b01.content.amp - content of the first block in the second template stack
89 | * s02.b01.super.amp - super content of the first block in the second template stack
90 |
91 | ## 2. Hover code snippets
92 |
93 | Now you can mouse hover a function name in your code and a small popup window including documentation on this function will show up. Check a small example below
94 |
95 | 
96 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MCFS [AMPScript]: Virtual filesystem for Marketing Cloud, syntax highlighting, code snippets and more
2 |
3 | 
4 |
5 | AMPScript is the language used to program emails, content blocks, webpages and script activities in the Salesforce Marketing Cloud. It is not a simple task to write code directly in the UI of MC. This extensions helps you solve this problem.
6 |
7 | This extension allows you to connect Visual Studio Code directly to your MC Account, enables syntax highlighting for AMPScript, has built-in documentation for all AMPScript functions and also adds code snippets for language elements and functions. Each snippet includes a detailed description of the function and its parameters. Snippets also show up when you hover a function name.
8 |
9 | With direct connection to MC you can: easily change content in MC without leaving your text editor, save time and avoid frequent copy-pasting. It also helps you to better control the content of your emails, content blocks and cloud pages by exposing additional content attributes that are not available in the UI of MC.
10 |
11 | ### How to enable syntax highlighting
12 |
13 | You have two options on how to enable syntax highlighting:
14 |
15 | * Open a file that has an ".amp" or an ".ampscript" file extension
16 | * Manually set the language of the file to "AMPscript" (check the video below)
17 |
18 | 
19 |
20 |
21 | ### How to connect to Marketing Cloud
22 |
23 | As of now, you **can only edit existing assets** (content blocks, emails, cloudpage, json message, sql queries and dataextensions). Functionality that is not supported at the moment: create new asset, rename asset, move asset to a different folder, delete asset.
24 |
25 | * In your MC accout, create a new installed package and add a 'Server-to-Server' API integration Component
26 | * Add the following permissions:
27 | * CHANNELS: Email (Read and Write)
28 | * CHANNELS: Web (Read, Write, Publish)
29 | * ASSETS: Saved Content (Read and Write)
30 | * AUTOMATION: Automations (Read, Write, Execute)
31 | * DATA: Data Extensions (Read, Write)
32 | * Grant access to all required BUs
33 | * Provide package details in the connection manager below, save it and connect
34 | * You'll find the entire Content Builder library in your File Explorer tab
35 | * To open Connection Manager next time press F1 (or 'CMD+Shift+P' on Mac and 'CTRL+Shift+P' on Windows) and start typing 'MCFS'. Find 'MCFS Connecton Manager' and then hit Enter
36 |
37 | Detailed instructions with screenshots are available directly in the Connection Manager. To open Connection Manager press F1 (or 'CMD+Shift+P' on Mac and 'CTRL+Shift+P' on Windows) and start typing 'MCFS'. Find 'MCFS Connecton Manager' and then hit Enter.
38 |
39 | ### How to edit Content Builder assets directly from Visual Studio Code
40 |
41 | Each asset is presented as a folder that starts with an 'Ω' symbol. You can easily distinguish different asset types based on the colored square that goes after 'Ω':
42 | * 🟥 - blocks
43 | * 🟦 - emails
44 | * 🟨 - templates
45 | * 🟩 - cloudpages
46 | * 🟪 - mobile messages
47 |
48 | Each asset folder includes a readonly '__raw.readonly.json' file. This is an API representation of the asset. You can not modify. Instead you can modify all other files available under the asset folder. Each file represents a specific part of the asset. For the template based email you will see for example smth like:
49 | * _htmlcontent.amp - template used to create an email
50 | * _subject.amp - subject line of the email
51 | * _preheader.amp - preheader of the email
52 | * s01.b01.content.amp - content of the first content block (b01) that is located in the first template stack/placeholder (s01)
53 | * s01.b01.super.amp - super content of the block above. Learn more about super content [here](https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/design_super_content.htm)
54 | * s01.b02.content.amp - content of the second block in the first template stack
55 | * s01.b02.super.amp - super content of the second block in the first template stack
56 | * s02.b01.content.amp - content of the first block in the second template stack
57 | * s02.b01.super.amp - super content of the first block in the second template stack
58 |
59 | ### How to run an SQL query
60 | * Connect to your MC account
61 | * Find your SQL Query asset in the "SQL Queries" folder and open a "query.sql" file
62 | * Click a "Run SQL Query" button located in the top right corner of the editor (or run a "MCFS: Run SQL Query" command from the Command Pallet)
63 |
64 | 
65 |
66 |
67 | ### How to filter a Dataextension
68 | * Connect to your MC account
69 | * Find your Dataextension asset in the "Dataextensions" folder and open a "rows.csv" file
70 | * Click a "Filter a Dataextension" button located in the top right corner of the editor (or run a "MCFS: Filter a Dataextension" command from the Command Pallet)
71 | * Set the filter and hit enter
72 | * Filter example: OrderID = 'ORD2123F2' AND SubscriberKey = 'ABC'
73 |
74 | 
75 |
76 | ### How it looks and works
77 |
78 | #### Demo
79 |
80 | 
81 |
82 | #### Hover snippets
83 |
84 | 
85 |
86 | #### Code snippets
87 |
88 | 
89 |
90 | #### Syntax highlighting
91 |
92 | 
93 |
94 |
95 |
96 | #### Copyright 2017-2021 Sergey Agadzhanov
97 |
98 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
99 |
100 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
101 |
102 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
103 |
--------------------------------------------------------------------------------
/src/libs/connectionController.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as xml2js from 'xml2js';
4 | import { ApiRequestConfig, HttpUtils } from './httpUtils';
5 | import { Utils } from './utils';
6 |
7 | interface Token {
8 | rest_instance_url: string;
9 | soap_instance_url: string;
10 | access_token: string;
11 | token_type: string;
12 | expires_in: number;
13 | scope: string;
14 | expires: Date;
15 | }
16 |
17 | export interface Connection {
18 | name: string;
19 | account_id: string;
20 | authBaseUri: string;
21 | client_id: string;
22 | client_secret: string;
23 | grant_type: string | undefined;
24 | }
25 |
26 | export class APIException extends Error {
27 | public message: string;
28 | public details: string;
29 | public data: string;
30 | public readonly innerException: any;
31 |
32 | constructor(message: string, details: string, innerException?: any) {
33 | super();
34 | this.message = message;
35 | this.details = details;
36 | this.data = '';
37 | this.innerException = innerException;
38 |
39 | const data = this.innerException?.response?.data;
40 |
41 | if (data?.errors) {
42 | this.data = 'Errors: ' + data?.errors?.map((err: any) => err.message)?.join(' | ') || '';
43 | }
44 | else if (data?.error_description) {
45 | this.data = 'Errors: ' + data?.error_description;
46 | }
47 | else {
48 | this.data = JSON.stringify(data);
49 | }
50 | }
51 | }
52 |
53 | export enum SoapOperation {
54 | RETRIEVE = "Retrieve",
55 | UPDATE = "Update"
56 | }
57 |
58 | export interface SoapRequestConfig {
59 | operation: SoapOperation;
60 | transformResponse?: (responseBody: any) => any;
61 | body: any;
62 | }
63 |
64 | export class ConnectionController {
65 | private connections: Map;
66 | private tokens: Map>;
67 |
68 | private static instance: ConnectionController | null = null;
69 |
70 | constructor() {
71 | this.connections = new Map();
72 | this.tokens = new Map>();
73 | }
74 |
75 | static getInstance(): ConnectionController {
76 | if (ConnectionController.instance === null) {
77 | ConnectionController.instance = new ConnectionController();
78 | }
79 | return ConnectionController.instance;
80 | }
81 |
82 | setConnections(connections: Array): void {
83 | connections.forEach(c => {
84 | if (c.grant_type === undefined) {
85 | c.grant_type = 'client_credentials'
86 | }
87 | this.connections.set(c.account_id, c);
88 | });
89 | }
90 |
91 | async hasTokenRequiredScopes(connectionId: string, scopes: Array): Promise {
92 | const token = await this.getToken(connectionId);
93 |
94 | for (const scope of scopes) {
95 | if (!token.scope.includes(scope)) return false;
96 | }
97 |
98 | return true;
99 | }
100 |
101 | async getToken(connectionId: string): Promise {
102 | const pToken: Promise = this.tokens.get(connectionId) || this.refreshToken(connectionId);
103 | let error: any = undefined;
104 |
105 | try {
106 | let token = await pToken;
107 |
108 | if (token.expires == null || token.expires < new Date()) {
109 | token = await this.refreshToken(connectionId);
110 | }
111 |
112 | if (token !== undefined) {
113 | return token;
114 | }
115 | }
116 | catch (err: any) {
117 | error = err;
118 | }
119 |
120 | throw new APIException(
121 | 'Connection Issue',
122 | `Failed to get a token for connection "${connectionId}"
123 | Check your configuration and make sure that all required
124 | permissions were set for the installed Package`,
125 | error
126 | );
127 | }
128 |
129 | async refreshToken(connectionId: string): Promise {
130 | const connection = this.connections.get(connectionId);
131 |
132 | if (connection === undefined) {
133 | throw new APIException(
134 | 'Connection Issue',
135 | `Connection "${connectionId}" has not been found in the Connection Manager`);
136 | }
137 |
138 | const now = new Date().getTime();
139 |
140 | const apiConfig = new ApiRequestConfig({
141 | baseURL: connection.authBaseUri,
142 | method: "POST",
143 | url: "/v2/token",
144 | data: JSON.stringify(connection)
145 | });
146 |
147 | const pToken = HttpUtils.getInstance().makeRestApiCall(apiConfig).then(tokenData => {
148 | const token = tokenData as Token;
149 | token.expires = new Date(now + (token.expires_in - 5) * 1000);
150 | Utils.getInstance().log(`Token object is ready for ${connectionId}: ${JSON.stringify(token.token_type)}`);
151 | return token;
152 | }).catch(err => {
153 | Utils.getInstance().logError(err);
154 | throw err;
155 | });
156 |
157 | this.tokens.set(connectionId, pToken);
158 |
159 | Utils.getInstance().log(`Token for ${connectionId} is added to the connections pool: size=${this.tokens?.size}`);
160 |
161 | return pToken;
162 | }
163 |
164 | async restRequest(connectionId: string, config: ApiRequestConfig): Promise {
165 | try {
166 | const token: Token = await this.getToken(connectionId);
167 |
168 | config.baseURL = token.rest_instance_url;
169 | config.headers = {
170 | 'Authorization': `${token.token_type} ${token.access_token}`
171 | };
172 |
173 | const data = await HttpUtils.getInstance().makeRestApiCall(config);
174 | return data;
175 |
176 | }
177 | catch (ex: any) {
178 | throw (ex instanceof APIException ? ex : new APIException('REST API failed', ex.message, ex));
179 | }
180 | }
181 |
182 | async soapRequest(connectionId: string, config: SoapRequestConfig): Promise {
183 | try {
184 | const token: Token = await this.getToken(connectionId);
185 | const xmlBuilder = new xml2js.Builder();
186 |
187 | const body: any = {
188 | Envelope: {
189 | $: {
190 | "xmlns": "http://schemas.xmlsoap.org/soap/envelope/",
191 | "xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
192 | "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance"
193 | },
194 | Header: {
195 | fueloauth: {
196 | $: {
197 | "xmlns": "http://exacttarget.com"
198 | },
199 | _: token.access_token
200 | }
201 | },
202 | Body: config.body
203 | }
204 | };
205 |
206 | const requestConfig: ApiRequestConfig = new ApiRequestConfig({
207 | baseURL: token.soap_instance_url,
208 | url: '/Service.asmx',
209 | method: 'post',
210 | data: xmlBuilder.buildObject(body),
211 | headers: {
212 | 'Content-Type': 'text/xml',
213 | 'SOAPAction': config.operation
214 | }
215 | });
216 |
217 | const responseData = await HttpUtils.getInstance().makeApiCall(requestConfig);
218 | const parser = new xml2js.Parser();
219 |
220 | return parser.parseStringPromise(responseData).then(result => {
221 | const body = result["soap:Envelope"]["soap:Body"];
222 | return config.transformResponse === undefined ? body : config.transformResponse(body);
223 | });
224 | }
225 | catch (ex: any) {
226 | throw (ex instanceof APIException ? ex : new APIException('SOAP API failed', ex.message, ex));
227 | }
228 | }
229 |
230 | }
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as vscode from 'vscode';
4 | import * as path from 'path';
5 | import { MCFS } from './mcfsFileSystemProvider';
6 | import { Connection } from './libs/connectionController';
7 | import { Utils, WebPanel, WebPanelMessage } from './libs/utils';
8 | import { ConnectionController } from './libs/connectionController';
9 | import { FolderManagerUri } from './libs/folderManagerUri';
10 | import { FolderController } from './libs/folderController';
11 |
12 | let isConnectionManagerOpened = false;
13 |
14 | export async function activate(context: vscode.ExtensionContext) {
15 | try {
16 | context.subscriptions.push(Utils.getInstance().telemetry);
17 | Utils.getInstance().sendTelemetryEvent("activated");
18 | Utils.getInstance().log('MCFS extension activated');
19 |
20 | const mcfs = new MCFS();
21 |
22 | let connections = Utils.getInstance().getConfig('connections');
23 |
24 | ConnectionController.getInstance().setConnections(connections);
25 |
26 | const panel = new WebPanel('mcfs_connection_manager', 'MCFS Connection Manager');
27 |
28 | const openConnectionManager = () => {
29 | Utils.getInstance().sendTelemetryEvent("connection-manager");
30 | Utils.getInstance().setConfigField('notifications', 'hasOpenedConnectionManager', true);
31 |
32 | panel.onMessageReceived = (message: any) => {
33 |
34 | switch (message?.action) {
35 | case 'SEND_CONFIGS':
36 | panel.postMessage({
37 | action: 'SET_CONFIGS',
38 | content: connections
39 | } as WebPanelMessage);
40 | break;
41 |
42 | case 'CONNECT':
43 | connect(message.content as Connection);
44 | Utils.getInstance().setConfigField('notifications', 'hasConnectedToMC', true);
45 | panel.close();
46 | break;
47 |
48 | case 'UPDATE':
49 | connections = message.content;
50 | ConnectionController.getInstance().setConnections(connections);
51 | Utils.getInstance().setConfig('connections', connections);
52 | Utils.getInstance().showInformationMessage('Connections saved. Press "Connect" and then open File Explorer');
53 | break;
54 | }
55 | };
56 |
57 | panel.open(path.join(context.extensionPath, 'connection-manager'));
58 | };
59 |
60 | context.subscriptions.push(vscode.workspace.registerFileSystemProvider('mcfs', mcfs, { isCaseSensitive: false }));
61 |
62 | context.subscriptions.push(vscode.commands.registerCommand('mcfs.open', _ => {
63 | isConnectionManagerOpened = true;
64 | openConnectionManager();
65 | }));
66 |
67 | const registeredCommands: Array = [];
68 |
69 | FolderController.getInstance().customActions.forEach((a) => {
70 | if(registeredCommands.includes(a.command)) return;
71 |
72 | registeredCommands.push(a.command);
73 |
74 | context.subscriptions.push(vscode.commands.registerTextEditorCommand(a.command, async (textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, args: any[]) => {
75 | Utils.getInstance().sendTelemetryEvent(`customaction-${a.command}`);
76 |
77 | const uri = textEditor?.document?.uri;
78 |
79 | if (uri === undefined) return;
80 |
81 | const fmUri = new FolderManagerUri(uri);
82 | const currentContent = textEditor.document.getText();
83 |
84 | vscode.window.withProgress({
85 | location: vscode.ProgressLocation.Notification,
86 | title: a.waitLabel,
87 | cancellable: true
88 | }, async (progress, token) => {
89 | const result = await FolderController.getInstance().getManager(fmUri.mountFolderName)
90 | ?.customActions
91 | ?.find((c) => c.command == a.command)
92 | ?.callback(fmUri, currentContent);
93 |
94 | if (result !== undefined && currentContent !== result) {
95 | textEditor.edit((editBuilder) => {
96 | editBuilder.replace(new vscode.Selection(0, 0, textEditor.document.lineCount, 0), result);
97 | });
98 | }
99 |
100 | return;
101 | });
102 | }));
103 | });
104 |
105 | setTimeout(_ => {
106 | if (!isConnectionManagerOpened) {
107 | if (!showPromoPage(context.extensionPath)) {
108 | showPromoBanner(openConnectionManager);
109 | }
110 | }
111 | }, 5000);
112 |
113 | enableSnippets(context.extensionPath);
114 | }
115 | catch (err) {
116 | Utils.getInstance().showErrorMessage(err);
117 | }
118 | }
119 |
120 | export function deactivate() {
121 | Utils.getInstance().telemetry.dispose();
122 | }
123 |
124 | function connect(connection: Connection): void {
125 | Utils.getInstance().sendTelemetryEvent("connect");
126 |
127 | const mcfsUri = vscode.Uri.parse('mcfs://' + connection.account_id + '/');
128 |
129 | //TODO: replace folder
130 |
131 | if (undefined === vscode.workspace.getWorkspaceFolder(mcfsUri)) {
132 | vscode.workspace.updateWorkspaceFolders(
133 | vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders.length : 0, 0,
134 | {
135 | uri: mcfsUri,
136 | name: `MCFS_${connection.account_id}: ${connection.name}`
137 | }
138 | );
139 | }
140 |
141 | vscode.commands.executeCommand('workbench.view.explorer');
142 | Utils.getInstance().showInformationMessage(`Connected to ${connection.account_id}. Open File Explorer...`);
143 | }
144 |
145 | function enableSnippets(extensionPath: string) {
146 | Utils.getInstance().readJSON(extensionPath + '/syntaxes/snippets.json').then(snippets => {
147 | vscode.languages.registerHoverProvider('AMPscript', {
148 | provideHover(document, position, token) {
149 | let word = document.getText(document.getWordRangeAtPosition(position));
150 |
151 | if (!word || word.length > 100) {
152 | return null;
153 | }
154 |
155 | word = word.toLowerCase();
156 |
157 | if (snippets[word] !== undefined && snippets[word].description) {
158 | return {
159 | contents: [snippets[word].description]
160 | };
161 | }
162 | return null;
163 | }
164 | });
165 | });
166 | }
167 |
168 | function showPromoBanner(connectionManagerCallback: () => void) {
169 | const notifications = Utils.getInstance().getConfig('notifications');
170 |
171 | if (!notifications || notifications["dontShowConnectionManagerAlert"] || notifications["hasConnectedToMC"]) {
172 | return;
173 | }
174 |
175 | vscode.window.showInformationMessage(
176 | `Would you like to connect VSCode directly to Marketing Cloud?`,
177 | "YES, SOUNDS INTERESTING",
178 | "CHECK ON GITHUB",
179 | "NO")
180 | .then(selection => {
181 | if (selection == "YES, SOUNDS INTERESTING") {
182 | connectionManagerCallback();
183 | }
184 | else if (selection == "CHECK ON GITHUB") {
185 | vscode.env.openExternal(vscode.Uri.parse('https://github.com/Bizcuit/vscode-ampscript'));
186 | }
187 | else if (selection == "NO") {
188 | Utils.getInstance().setConfigField('notifications', 'dontShowConnectionManagerAlert', true);
189 | }
190 | });
191 | }
192 |
193 | function showPromoPage(externsionPath: string) {
194 | const notifications = Utils.getInstance().getConfig('notifications');
195 | const version = Utils.extensionVersion.split(".", 2).join(".");
196 |
197 | if (notifications["hasSeenPromoForVersion"] === version) {
198 | return false;
199 | }
200 |
201 | const uri = vscode.Uri.file(path.join(externsionPath, 'PROMO.md'))
202 |
203 | Utils.getInstance().setConfigField('notifications', 'hasSeenPromoForVersion', version);
204 |
205 | vscode.commands.executeCommand('markdown.showPreview', uri);
206 |
207 | return true;
208 | }
--------------------------------------------------------------------------------
/syntaxes/ampscript.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "name": "AMPScript",
4 | "scopeName": "source.amp",
5 | "keyEquivalent": "@A",
6 | "foldingStartMarker": "%%\\[\\s*$",
7 | "foldingStopMarker": "^\\s*\\]%%$",
8 | "injections": {
9 | "R:comment.block,comment.block.html,meta.attribute": {
10 | "patterns": [
11 | {
12 | "include": "#ampscript"
13 | },
14 | {
15 | "include": "#ampscript-substitutions"
16 | }
17 | ]
18 | }
19 | },
20 | "patterns": [
21 | {
22 | "include": "#ampscript"
23 | },
24 | {
25 | "include": "#ampscript-substitutions"
26 | },
27 | {
28 | "include": "text.html.basic"
29 | }
30 | ],
31 | "repository": {
32 | "ampscript": {
33 | "name": "meta.embedded.amp",
34 | "begin": "(%%[=\\[])",
35 | "end": "([\\]=]%%)",
36 | "beginCaptures": {
37 | "1": {
38 | "name": "keyword.other.namespace.amp"
39 | }
40 | },
41 | "endCaptures": {
42 | "1": {
43 | "name": "keyword.other.namespace.amp"
44 | }
45 | },
46 | "patterns": [
47 | {
48 | "include": "#ampscript-comments"
49 | },
50 | {
51 | "include": "#ampscript-functions"
52 | },
53 | {
54 | "include": "#ampscript-numeric"
55 | },
56 | {
57 | "include": "#ampscript-contstants"
58 | },
59 | {
60 | "include": "#ampscript-language-elements"
61 | },
62 | {
63 | "include": "#ampscript-strings"
64 | }
65 | ]
66 | },
67 | "ampscript-comments": {
68 | "patterns": [
69 | {
70 | "name": "comment.block.amp",
71 | "begin": "/\\*",
72 | "captures": {
73 | "0": {
74 | "name": "punctuation.definition.comment.amp"
75 | }
76 | },
77 | "end": "\\*/"
78 | }
79 | ]
80 | },
81 | "ampscript-functions": {
82 | "name": "support.function.amp",
83 | "match": "((?i:addobjectarrayitem|createobject|invokecreate|invokedelete|invokeexecute|invokeperform|invokeretrieve|invokeupdate|raiseerror|setobjectproperty|upsertcontact|attachfile|barcodeurl|beginimpressionregion|buildoptionlist|buildrowsetfromstring|buildrowsetfromxml|contentarea|contentblockbyid|contentareabyname|contentblockbyid|contentblockbykey|contentblockbyname|contentimagebyid|contentimagebykey|createsmsconversation|endimpressionregion|endsmsconversation|getportfolioitem|image|setsmsconversationnextkeyword|transformxml|treatascontent|treatascontentarea|wat|watp|claimrow|claimrowvalue|dataextensionrowcount|deletedata|deletede|executefilter|executefilterorderedrows|field|insertdata|insertde|lookup|lookuporderedrows|lookuprows|lookuprowscs|row|rowcount|updatedata|updatede|upsertdata|upsertde|dateadd|datediff|dateparse|datepart|formatdate|localdatetosystemdate|now|systemdatetolocaldate|base64decode|base64encode|decryptsymmetric|encryptsymmetric|guid|md5|sha1|sha256|sha512|httpget|httppost|httppost2|httprequestheader|ischtmlbrowser|redirectto|urlencode|wraplongurl|add|divide|formatcurrency|formatnumber|mod|multiply|random|subtract|addmscrmlistmember|createmscrmrecord|describemscrmentities|describemscrmentityattributes|retrievemscrmrecords|retrievemscrmrecordsfetchxml|setstatemscrmrecord|updatemscrmrecords|upsertmscrmrecord|createsalesforceobject|longsfid|retrievesalesforcejobsources|retrievesalesforceobjects|updatesinglesalesforceobject|authenticatedemployeeid|authenticatedemployeenotificationaddress|authenticatedemployeeusername|authenticatedenterpriseid|authenticatedmemberid|authenticatedmembername|cloudpagesurl|isnulldefault|livecontentmicrositeurl|micrositeurl|queryparameter|redirect|requestparameter|getpublishedsocialcontent|getsocialpublishurl|getsocialpublishurlbyname|char|concat|format|indexof|length|lowercase|propercase|regexmatch|replace|replacelist|stringtodate|stringtohex|substring|trim|uppercase|attributevalue|domain|empty|iif|isemailaddress|isnull|isphonenumber|output|outputline|v)\\b)(?=\\()"
84 | },
85 | "ampscript-numeric": {
86 | "name": "constant.numeric.amp",
87 | "match": "\\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\\.[0-9]+)?))\\b"
88 | },
89 | "ampscript-contstants": {
90 | "patterns": [
91 | {
92 | "name": "constant.language.boolean.true.amp",
93 | "match": "((?i:true)\\b)"
94 | },
95 | {
96 | "name": "constant.language.boolean.false.amp",
97 | "match": "((?i:false)\\b)"
98 | },
99 | {
100 | "name": "constant.language.boolean.null.amp",
101 | "match": "((?i:null)\\b)"
102 | }
103 | ]
104 | },
105 | "ampscript-language-elements": {
106 | "patterns": [
107 | {
108 | "name": "keyword.control.amp",
109 | "match": "((?i:do|else|elseif|for|if|endif|next|then|to|downto)\\b)"
110 | },
111 | {
112 | "name": "storage.type.amp",
113 | "match": "((?i:var|set)\\b)"
114 | },
115 | {
116 | "name": "variable.parameter.amp",
117 | "match": "\\@[a-zA-Z0-9_]+"
118 | },
119 | {
120 | "name": "variable.parameter.amp",
121 | "match": "\\[[a-zA-Z0-9_]+\\]"
122 | },
123 | {
124 | "name": "variable.language.amp",
125 | "match": "\\b((?i:xtmonth|xtmonthnumeric|xtday|xtdayofweek|xtyear|xtshortdate|xtlongdate|linkname|linkname|emailname_|_messagecontext|_messagetypepreference|_replycontent|_istestsend|jobid|_preheader|double_opt_in_url|emailaddr|fullname_|fullname|firstname_|firstname|lastname_|lastname|comment_|comment|subscriberid|_subscriberkey|listid|list_|listsubid|_messagetypepreference|mobile_number|short_code|_listname|_emailid|_jobsubscriberbatchid|_datasourcename|_impressionregionid|_impressionregionname|replyname|replyemailaddress|memberid|member_busname|member_addr|member_city|member_state|member_postalcode|member_country|view_email_url|ftaf_url|subscription_center_url|profile_center_url|unsub_center_url|mobile_number|short_code|line_address_id|line_job_id|line_subscriber_id|additionalinfo_|__additionalemailattribute1|__additionalemailattribute2|__additionalemailattribute3|__additionalemailattribute4|__additionalemailattribute5))\\b"
126 | },
127 | {
128 | "name": "support.class.amp",
129 | "match": "((?i:and|or|not)\\b)"
130 | },
131 | {
132 | "name": "variable.operator.amp",
133 | "match": "==|!=|>|<|>=|<=|="
134 | }
135 | ]
136 | },
137 | "ampscript-strings": {
138 | "patterns": [
139 | {
140 | "name": "string.quoted.double.amp",
141 | "begin": "\"",
142 | "end": "\"",
143 | "beginCaptures": {
144 | "0": {
145 | "name": "punctuation.definition.string.begin.amp"
146 | }
147 | },
148 | "endCaptures": {
149 | "0": {
150 | "name": "punctuation.definition.string.end.amp"
151 | }
152 | },
153 | "patterns": [
154 | {
155 | "name": "constant.character.escape.amp",
156 | "match": "\"\""
157 | }
158 | ]
159 | },
160 | {
161 | "name": "string.quoted.single.amp",
162 | "begin": "'",
163 | "end": "'",
164 | "beginCaptures": {
165 | "0": {
166 | "name": "punctuation.definition.string.begin.amp"
167 | }
168 | },
169 | "endCaptures": {
170 | "0": {
171 | "name": "punctuation.definition.string.end.amp"
172 | }
173 | },
174 | "patterns": [
175 | {
176 | "name": "constant.character.escape.amp",
177 | "match": "''"
178 | }
179 | ]
180 | }
181 | ]
182 | },
183 | "ampscript-substitutions": {
184 | "name": "meta.embedded.amp",
185 | "begin": "(%%)",
186 | "end": "(%%)",
187 | "beginCaptures": {
188 | "1": {
189 | "name": "keyword.other.namespace.amp"
190 | }
191 | },
192 | "endCaptures": {
193 | "1": {
194 | "name": "keyword.other.namespace.amp"
195 | }
196 | },
197 | "patterns": [
198 | {
199 | "name": "variable.parameter.amp",
200 | "match": "[a-zA-Z0-9_]+"
201 | }
202 | ]
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/src/libs/folderManagers/sqlQueries.ts:
--------------------------------------------------------------------------------
1 | import { Asset, AssetFile } from '../asset';
2 | import { FolderManagerUri } from '../folderManagerUri';
3 | import { FolderManager, Directory, CustomAction } from '../folderManager';
4 | import { ConnectionController } from '../connectionController';
5 | import { Utils } from '../utils';
6 | import { ApiRequestConfig } from '../httpUtils';
7 |
8 |
9 | export class SqlQueriesFolderManager extends FolderManager {
10 | readonly mountFolderName: string = "SQL Queries";
11 | private directoriesCache: Map>>;
12 |
13 | constructor() {
14 | super();
15 | this.directoriesCache = new Map>>();
16 |
17 | this.customActions.push({
18 | command: "mcfs.query.run",
19 | waitLabel: "Running a Query",
20 | callback: (fmUri: FolderManagerUri, content: string): Promise => this.customActionRunQuery(fmUri, content)
21 | } as CustomAction);
22 | }
23 |
24 | /* Interface implementation */
25 |
26 | async getAssetsInDirectory(directoryUri: FolderManagerUri): Promise {
27 | const directoryId: number = await this.getDirectoryId(directoryUri);
28 |
29 | const hasTokenScopes = await ConnectionController.getInstance().hasTokenRequiredScopes(
30 | directoryUri.connectionId,
31 | ['automations_execute', 'automations_read', 'automations_write']
32 | );
33 |
34 | if (!hasTokenScopes) {
35 | Utils.getInstance().sendTelemetryEvent("error.manager-sqlqueries.missing_api_scope", true, true);
36 | throw new Error('Additional permissions are required for this function: AUTOMATION: Automations (Read, Write, Execute). Please update your installed package and restart VSCode');
37 | }
38 |
39 | const config = new ApiRequestConfig({
40 | method: 'get',
41 | url: `/automation/v1/queries/category/${directoryId}`,
42 | params: {
43 | '$page': 1,
44 | '$pageSize': 100,
45 | 'retrievalType': 1
46 | }
47 | });
48 |
49 | const data: any = await ConnectionController.getInstance().restRequest(directoryUri.connectionId, config);
50 |
51 | const assets: Array = new Array();
52 |
53 | (data.items as Array).forEach(a => {
54 | const asset: Asset = new Asset(
55 | a.name || '???',
56 | //AssetSubtype.SQL,
57 | this.getAssetDirectoryName(a.name, a),
58 | JSON.stringify(a, null, 2),
59 | directoryUri.connectionId,
60 | this.extractFiles(a)
61 | );
62 | //this.assetsCache.set(directoryUri.getChildPath(asset.fsName), asset);
63 | this.assetsCache.set(directoryUri.getChildPath(asset.directoryName), asset);
64 | assets.push(asset);
65 | });
66 |
67 | return assets;
68 | }
69 |
70 | async getSubdirectories(directoryUri: FolderManagerUri): Promise {
71 | const directoryId: number = await this.getDirectoryId(directoryUri);
72 |
73 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(directoryUri, directoryId);
74 | return subdirectories.map(d => d.name);
75 | }
76 |
77 | async saveAsset(asset: Asset): Promise {
78 | const assetData: any = JSON.parse(asset.content);
79 |
80 | const config = new ApiRequestConfig({
81 | method: 'patch',
82 | url: `/automation/v1/queries/${assetData.queryDefinitionId}`,
83 | data: assetData
84 | });
85 |
86 | await ConnectionController.getInstance().restRequest(asset.connectionId, config);
87 | }
88 |
89 | async setAssetFile(asset: Asset, file: AssetFile): Promise {
90 | const assetData: any = JSON.parse(asset.content);
91 | assetData[file.path] = file.content;
92 | asset.content = JSON.stringify(assetData, null, 2);
93 | }
94 |
95 | getAssetDirectoryName(name: string, assetData: any): string {
96 | return `Ω 🟥 ${name}.query`;
97 | }
98 |
99 | getFileExtensions(): Array {
100 | return ['.sql', '.json'];
101 | }
102 |
103 | /* Support methods */
104 |
105 | public async customActionRunQuery(fmUri: FolderManagerUri, content: string): Promise {
106 | const assetUri = fmUri.isAsset ? fmUri : fmUri.parent;
107 |
108 | if (assetUri === undefined) return;
109 |
110 | const asset = await this.getAsset(assetUri, false);
111 | const assetMetadata: any = JSON.parse(asset.content);
112 | const queryId = assetMetadata?.["queryDefinitionId"];
113 |
114 | if (!queryId) return;
115 |
116 | await this.runQuery(queryId, fmUri.connectionId);
117 |
118 | const retriesDelay = 6000;
119 | const maxNumberOfRetries = 20;
120 | let currentRetry = 0;
121 | let hasFinished = false;
122 |
123 | while (maxNumberOfRetries > currentRetry++) {
124 | await Utils.getInstance().delay(retriesDelay);
125 | const isRunning = await this.isQueryRunning(queryId, fmUri.connectionId);
126 |
127 | if (!isRunning) {
128 | hasFinished = true;
129 | break;
130 | }
131 | }
132 |
133 | if (hasFinished) {
134 | Utils.getInstance().showInformationMessage("Query executed successfully");
135 | }
136 | else {
137 | Utils.getInstance().showErrorMessage("Query wait timeout");
138 | }
139 |
140 | return;
141 | }
142 |
143 |
144 |
145 | private async runQuery(queryId: string, connectionId: string): Promise {
146 | const config = new ApiRequestConfig({
147 | method: 'post',
148 | url: `automation/v1/queries/${queryId}/actions/start`
149 | });
150 |
151 | await ConnectionController.getInstance().restRequest(connectionId, config);
152 | return;
153 | }
154 |
155 | private async isQueryRunning(queryId: string, connectionId: string): Promise {
156 | const config = new ApiRequestConfig({
157 | method: 'get',
158 | url: `automation/v1/queries/${queryId}/actions/isrunning`
159 | });
160 |
161 | const data: any = await ConnectionController.getInstance().restRequest(connectionId, config);
162 |
163 | Utils.getInstance().log(JSON.stringify(data));
164 |
165 | return data.isRunning;
166 | }
167 |
168 | private extractFiles(assetData: any): Array {
169 | const result: Array = [];
170 |
171 | if (assetData?.queryText !== undefined) {
172 | result.push(new AssetFile(
173 | "query.sql",
174 | assetData?.queryText,
175 | "queryText"
176 | ));
177 | }
178 |
179 | return result;
180 | }
181 |
182 | private async getAllDirectories(connectionId: string): Promise> {
183 | const cached = this.directoriesCache.get(connectionId);
184 |
185 | if (cached !== undefined) {
186 | return cached;
187 | }
188 |
189 | const config = new ApiRequestConfig({
190 | method: 'get',
191 | url: '/automation/v1/folders/',
192 | params: {
193 | '$pagesize': '200',
194 | '$filter': `categorytype eq queryactivity`
195 | }
196 | });
197 |
198 | const pDirectories = ConnectionController.getInstance()
199 | .restRequest(connectionId, config)
200 | .then(response => {
201 | const directories = new Array();
202 |
203 | response?.items?.forEach((e: any) => {
204 | directories.push({
205 | id: e.categoryId,
206 | parentId: e.parentId,
207 | name: e.name
208 | } as Directory);
209 | });
210 |
211 | return directories;
212 | });
213 |
214 | this.directoriesCache.set(connectionId, pDirectories);
215 |
216 | return pDirectories;
217 | }
218 |
219 | private async getSubdirectoriesByDirectoryId(uri: FolderManagerUri, directoryId: number): Promise> {
220 | const allDirectories = await this.getAllDirectories(uri.connectionId);
221 |
222 | return allDirectories.filter(d => d.parentId === directoryId);
223 | }
224 |
225 | private async getDirectoryId(uri: FolderManagerUri): Promise {
226 | const allDirectories = await this.getAllDirectories(uri.connectionId);
227 |
228 | if (uri.localPath === '') {
229 | const root: Directory | undefined = allDirectories.find(d => d.parentId === 0);
230 | if (root !== undefined) {
231 | return root.id;
232 | }
233 | }
234 |
235 | if (uri.parent !== undefined) {
236 | const parentDirectoryId = await this.getDirectoryId(uri.parent);
237 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(uri, parentDirectoryId);
238 |
239 | for (const d of subdirectories) {
240 | if (d.name === uri.name) {
241 | return d.id;
242 | }
243 | }
244 | }
245 |
246 | throw new Error(`Path not found: ${uri.globalPath}`);
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/libs/folderManagers/contentBuilder.ts:
--------------------------------------------------------------------------------
1 | import { Asset, AssetFile } from '../asset';
2 | import { ConnectionController } from '../connectionController';
3 | import { FolderManager, Directory } from '../folderManager';
4 | import { FolderManagerUri } from '../folderManagerUri';
5 | import { ApiRequestConfig } from '../httpUtils';
6 |
7 | export enum AssetType {
8 | UNKNOWN = 0,
9 | BLOCK = 1,
10 | TEMPLATE = 2,
11 | EMAIL = 3,
12 | WEBPAGE = 4,
13 | JSON_MESSAGE = 5
14 | }
15 |
16 | export enum AssetSubtype {
17 | UNKNOWN = 0,
18 | TEMPLATE = 4,
19 | EMAIL_HTML = 208,
20 | EMAIL_TEMPLATEBASED = 207,
21 | EMAIL_TEXT = 209,
22 | BLOCK_CODESNIPPET = 220,
23 | BLOCK_FREEFORM = 195,
24 | BLOCK_TEXT = 196,
25 | BLOCK_HTML = 197,
26 | WEBPAGE = 205,
27 | JSON_MESSAGE = 230
28 | }
29 |
30 | export const ContentBuilderStandardTypes = [
31 | AssetSubtype.TEMPLATE,
32 | AssetSubtype.EMAIL_HTML,
33 | AssetSubtype.EMAIL_TEMPLATEBASED,
34 | AssetSubtype.EMAIL_TEXT,
35 | AssetSubtype.BLOCK_CODESNIPPET,
36 | AssetSubtype.BLOCK_FREEFORM,
37 | AssetSubtype.BLOCK_TEXT,
38 | AssetSubtype.BLOCK_HTML,
39 | AssetSubtype.JSON_MESSAGE
40 | ];
41 |
42 | export class ContentBuilderFolderManager extends FolderManager {
43 | readonly mountFolderName: string;
44 |
45 | private directoriesCache: Map;
46 | private readonly assetSubtypesFilter: Array;
47 | private readonly readOnlyRootFolder: boolean;
48 | private readonly isSharedFolderScope: boolean;
49 |
50 | constructor(
51 | mountFolderName: string,
52 | isSharedFolderScope = false,
53 | assetSubtypesFilter: Array,
54 | readOnlyRootFolder: boolean
55 | ) {
56 | super();
57 | this.directoriesCache = new Map();
58 | this.mountFolderName = mountFolderName;
59 | this.assetSubtypesFilter = assetSubtypesFilter;
60 | this.readOnlyRootFolder = readOnlyRootFolder;
61 | this.isSharedFolderScope = isSharedFolderScope;
62 | }
63 |
64 | /* Interface implementation */
65 | async getSubdirectories(directoryUri: FolderManagerUri): Promise {
66 | const directoryId: number = await this.getDirectoryId(directoryUri);
67 |
68 | if (this.readOnlyRootFolder && directoryId != 0) return [];
69 |
70 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(directoryUri, directoryId);
71 |
72 | return subdirectories.map(d => d.name);
73 | }
74 |
75 | async getAssetsInDirectory(directoryUri: FolderManagerUri): Promise {
76 | const directoryId: number = await this.getDirectoryId(directoryUri);
77 | return this.getAssetsByDirectoryId(directoryUri, directoryId);
78 | }
79 |
80 | async saveAsset(asset: Asset): Promise {
81 | const assetData: any = JSON.parse(asset.content);
82 |
83 | const data: any = {
84 | id: assetData.id
85 | };
86 |
87 | if (assetData.content) {
88 | data['content'] = assetData.content;
89 | }
90 |
91 | if (assetData.views) {
92 | data['views'] = assetData.views;
93 | }
94 |
95 |
96 | const config = new ApiRequestConfig({
97 | method: 'patch',
98 | url: `/asset/v1/content/assets/${assetData.id}`,
99 | data: data
100 | });
101 |
102 | await ConnectionController.getInstance().restRequest(asset.connectionId, config);
103 | }
104 |
105 | async setAssetFile(asset: Asset, file: AssetFile): Promise {
106 | const assetData: any = JSON.parse(asset.content);
107 |
108 | if (file.path === '') {
109 | asset.content = file.content;
110 | }
111 | else {
112 | const path = file.path.split('/');
113 | let ref: any = assetData;
114 |
115 | for (let i = 0; i < path.length - 1; i++) {
116 | ref = ref?.[path[i]];
117 | }
118 |
119 | const prop: string = path.pop() || '';
120 |
121 | if (typeof ref[prop] === "object") {
122 | ref[prop] = JSON.parse(file.content);
123 | }
124 | else {
125 | ref[prop] = file.content;
126 | }
127 |
128 | asset.content = JSON.stringify(assetData, null, 2);
129 | }
130 | }
131 |
132 | getAssetDirectoryName(name: string, assetData: any): string {
133 | const subtype: AssetSubtype = assetData?.assetType?.id as AssetSubtype || AssetSubtype.UNKNOWN;
134 | const type: AssetType = ContentBuilderFolderManager.getAssetTypeBySubtype(subtype);
135 |
136 | let suffix = '.unknown';
137 | let prefix = '⬛';
138 |
139 | switch (type) {
140 | case AssetType.BLOCK:
141 | prefix = '🟥';
142 | suffix = '.block';
143 | break;
144 |
145 | case AssetType.EMAIL:
146 | prefix = '🟦';
147 | suffix = '.email';
148 | break;
149 |
150 | case AssetType.TEMPLATE:
151 | prefix = '🟨';
152 | suffix = '.template';
153 | break;
154 |
155 | case AssetType.WEBPAGE:
156 | prefix = '🟩';
157 | suffix = '.cloudpage'
158 | break;
159 |
160 | case AssetType.JSON_MESSAGE:
161 | prefix = '🟪';
162 | suffix = '.jsonmessage'
163 | break;
164 |
165 | default:
166 | prefix = '⬛';
167 | suffix = '.unknown'
168 | break;
169 | }
170 |
171 | return `Ω ${prefix} ${name}${suffix}`;
172 | }
173 |
174 | getFileExtensions(): Array {
175 | return ['.amp', '.json'];
176 | }
177 |
178 | /* Support methods */
179 |
180 | private static getAssetTypeBySubtype(subtype: AssetSubtype): AssetType {
181 | switch (subtype) {
182 | case AssetSubtype.BLOCK_CODESNIPPET:
183 | case AssetSubtype.BLOCK_FREEFORM:
184 | case AssetSubtype.BLOCK_HTML:
185 | case AssetSubtype.BLOCK_TEXT:
186 | return AssetType.BLOCK;
187 | case AssetSubtype.EMAIL_HTML:
188 | case AssetSubtype.EMAIL_TEMPLATEBASED:
189 | case AssetSubtype.EMAIL_TEXT:
190 | return AssetType.EMAIL;
191 | case AssetSubtype.TEMPLATE:
192 | return AssetType.TEMPLATE;
193 | case AssetSubtype.WEBPAGE:
194 | return AssetType.WEBPAGE;
195 | case AssetSubtype.JSON_MESSAGE:
196 | return AssetType.JSON_MESSAGE;
197 | }
198 | return AssetType.UNKNOWN;
199 | }
200 |
201 | private async getAssetsByDirectoryId(uri: FolderManagerUri, directoryId: number): Promise> {
202 | const config = new ApiRequestConfig({
203 | method: 'post',
204 | url: '/asset/v1/content/assets/query',
205 | data: {
206 | "page":
207 | {
208 | "page": 1,
209 | "pageSize": 100
210 | },
211 | "query":
212 | {
213 | "leftOperand":
214 | {
215 | "property": "category.id",
216 | "simpleOperator": "equal",
217 | "value": directoryId
218 | },
219 | "logicalOperator": "AND",
220 | "rightOperand":
221 | {
222 | "property": "assetType.id",
223 | "simpleOperator": "in",
224 | "value": this.assetSubtypesFilter
225 | }
226 | }
227 | }
228 | });
229 |
230 | const data: any = await ConnectionController.getInstance().restRequest(uri.connectionId, config);
231 |
232 | const assets: Array = new Array();
233 |
234 | (data.items as Array).forEach(a => {
235 | const asset = new Asset(
236 | a.name,
237 | this.getAssetDirectoryName(a.name, a),
238 | JSON.stringify(a, null, 2),
239 | uri.connectionId,
240 | this.extractFiles(a)
241 | );
242 |
243 | this.assetsCache.set(uri.getChildPath(asset.directoryName), asset)
244 | assets.push(asset);
245 | });
246 |
247 | return assets;
248 | }
249 |
250 | private extractFiles(assetData: any): Array {
251 | const result: Array = [];
252 |
253 | if (assetData?.views?.subjectline?.content !== undefined) {
254 | result.push(new AssetFile(
255 | "_subject.amp",
256 | assetData?.views?.subjectline?.content,
257 | "views/subjectline/content"
258 | ));
259 | }
260 |
261 | if (assetData?.views?.preheader?.content !== undefined) {
262 | result.push(new AssetFile(
263 | "_preheader.amp",
264 | assetData?.views?.subjectline?.content,
265 | "views/preheader/content"
266 | ));
267 | }
268 |
269 | if (assetData?.views?.html?.content !== undefined) {
270 | result.push(new AssetFile(
271 | '_htmlcontent.amp',
272 | assetData?.views?.html?.content || '',
273 | 'views/html/content',
274 | ));
275 | }
276 |
277 | /* Templates and emails */
278 | if (assetData?.content !== undefined) {
279 | result.push(new AssetFile(
280 | '_content.amp',
281 | assetData?.content || '',
282 | 'content'
283 | ));
284 | }
285 |
286 | /* JSON messages */
287 | if (assetData?.views !== undefined) {
288 | const views: any = assetData?.views;
289 |
290 | for (const viewName in views) {
291 | const data: any = assetData?.views[viewName]?.meta?.options?.customBlockData;
292 |
293 | if (data) {
294 | result.push(new AssetFile(
295 | viewName.toLowerCase() + '.json',
296 | JSON.stringify(data, null, 2),
297 | `views/${viewName}/meta/options/customBlockData`
298 | ));
299 |
300 | const fields = ["display:message", "display:message:display", "display:title", "display:title:display", "display:subtitle", "display:subtitle:display"];
301 |
302 | fields.forEach((f: string) => {
303 | if (data[f] !== undefined) {
304 | result.push(new AssetFile(
305 | f.toLowerCase().replace(/[:]/gi, '_') + '.amp',
306 | data[f],
307 | `views/${viewName}/meta/options/customBlockData/${f}`
308 | ));
309 | }
310 | })
311 | }
312 | }
313 | }
314 |
315 | /* Emails and Cloud Pages */
316 | if (assetData?.views?.html?.slots !== undefined) {
317 | const slots: any = assetData?.views?.html?.slots;
318 | let slotIndex = 0;
319 |
320 | for (const s in slots) {
321 | const slot = slots[s];
322 | const blocks = slot.blocks || {};
323 | let blockIndex = 1;
324 |
325 | slotIndex++;
326 |
327 | for (const b in blocks) {
328 | const block = blocks[b];
329 | const path = `views/html/slots/${s}/blocks/${b}/`;
330 | const slotName = 's' + (slotIndex < 10 ? '0' : '') + slotIndex;
331 | const blockName = 'b' + (blockIndex < 10 ? '0' : '') + blockIndex;
332 |
333 | blockIndex++;
334 |
335 | result.push(new AssetFile(
336 | `${slotName}.${blockName}.content.amp`,
337 | block.content || "",
338 | path + "content",
339 | ));
340 |
341 | result.push(new AssetFile(
342 | `${slotName}.${blockName}.super.amp`,
343 | block.superContent || "",
344 | path + "superContent"
345 | ));
346 | }
347 | }
348 |
349 | }
350 |
351 | return result;
352 | }
353 |
354 | private async getDirectoryId(uri: FolderManagerUri): Promise {
355 | if (this.directoriesCache.get(uri.globalPath) !== undefined) {
356 | return this.directoriesCache.get(uri.globalPath) || 0;
357 | }
358 |
359 | if (uri.localPath === '') {
360 | return this.getRootDirectoryId(uri);
361 | }
362 |
363 | if (uri.parent !== undefined) {
364 | const parentDirectoryId = await this.getDirectoryId(uri.parent);
365 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(uri, parentDirectoryId);
366 |
367 | for (const d of subdirectories) {
368 | if (d.name === uri.name) {
369 | return d.id;
370 | }
371 | }
372 | }
373 |
374 | throw new Error(`Path not found: ${uri.globalPath}`);
375 | }
376 |
377 | private async getSubdirectoriesByDirectoryId(uri: FolderManagerUri, directoryId: number): Promise> {
378 | const scope = this.isSharedFolderScope ? "Shared" : "Ours";
379 |
380 | const config = new ApiRequestConfig({
381 | method: 'get',
382 | url: '/asset/v1/content/categories/',
383 | params: {
384 | '$pagesize': '100',
385 | '$filter': `parentId eq ${directoryId}`,
386 | 'scope': scope
387 | }
388 | });
389 |
390 | const data: any = await ConnectionController.getInstance().restRequest(uri.connectionId, config);
391 |
392 | if (directoryId !== 0) {
393 | for (const d of data.items as Array) {
394 | this.directoriesCache.set(uri.getChildPath(d.name), d.id);
395 | }
396 | }
397 |
398 | return data.items as Array;
399 | }
400 |
401 | private async getRootDirectoryId(uri: FolderManagerUri): Promise {
402 | if (this.directoriesCache.get(uri.mountPath) !== undefined) {
403 | return this.directoriesCache.get(uri.mountPath) || 0;
404 | }
405 |
406 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(uri, 0);
407 |
408 | this.directoriesCache.set(uri.globalPath, subdirectories[0].id);
409 |
410 | return subdirectories[0].id;
411 | }
412 | }
413 |
--------------------------------------------------------------------------------
/src/libs/folderManagers/dataextensions.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Asset, AssetFile } from '../asset';
3 | import { ConnectionController, SoapOperation, SoapRequestConfig } from '../connectionController';
4 | import { FolderManager, Directory, CustomAction } from '../folderManager';
5 | import { FolderManagerUri } from '../folderManagerUri';
6 | import { Utils } from '../utils';
7 | import * as Papa from 'papaparse';
8 | import { SoapFilterExpression, SoapUtils } from '../soapUtils';
9 | import * as vscode from 'vscode';
10 |
11 | interface DataextensionColumn {
12 | Name: string;
13 | FieldType: string;
14 | ObjectID: string;
15 | MaxLength: string | undefined;
16 | Scale: string | undefined;
17 | IsRequired: string;
18 | IsPrimaryKey: string;
19 | DefaultValue: string | undefined;
20 | Ordinal: number;
21 | }
22 |
23 | export class DataextensionFolderManager extends FolderManager {
24 | readonly mountFolderName: string;
25 | readonly ignoreDirectories: boolean;
26 | readonly rootFolderCustomerKey: string;
27 | readonly folderContentType: string;
28 | private directoriesCache: Map;
29 | private filterString = "";
30 |
31 |
32 | constructor(
33 | mountFolderName: string,
34 | ignoreDirectories: boolean,
35 | rootFolderCustomerKey = "dataextension_default",
36 | folderContentType = "dataextension")
37 | {
38 | super();
39 |
40 | this.mountFolderName = mountFolderName;
41 | this.ignoreDirectories = ignoreDirectories;
42 | this.rootFolderCustomerKey = rootFolderCustomerKey;
43 | this.folderContentType = folderContentType;
44 | this.directoriesCache = new Map();
45 |
46 | this.customActions.push({
47 | command: "mcfs.dataextension.filter",
48 | waitLabel: "Filtering a Dataextension",
49 | callback: (fmUri: FolderManagerUri, content: string): Promise => this.customActionFilter(fmUri, content)
50 | } as CustomAction);
51 | }
52 |
53 | /* Interface implementation */
54 |
55 | getAssetDirectoryName(name: string, assetData: any): string {
56 | return `Ω 🟦 ${name}.dataext`;
57 | }
58 |
59 | getFileExtensions(): Array {
60 | return ['.csv', '.txt'];
61 | }
62 |
63 | async getSubdirectories(directoryUri: FolderManagerUri): Promise {
64 | const directoryId: number = await this.getDirectoryId(directoryUri);
65 |
66 | if (this.ignoreDirectories && directoryId != 0) return [];
67 |
68 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(directoryUri, directoryId);
69 |
70 | return subdirectories.map(d => d.name);
71 | }
72 |
73 | async getAssetsInDirectory(directoryUri: FolderManagerUri): Promise {
74 | const directoryId: number = await this.getDirectoryId(directoryUri);
75 |
76 | const assets = await ConnectionController.getInstance().soapRequest(directoryUri.connectionId, {
77 | operation: SoapOperation.RETRIEVE,
78 | body: SoapUtils.createRetrieveBody(
79 | "DataExtension",
80 | ["Name", "CustomerKey"],
81 | {
82 | Property: "CategoryID",
83 | SimpleOperator: "equals",
84 | Value: directoryId
85 | }
86 | ),
87 | transformResponse: (body) => {
88 | return SoapUtils.getArrProp(body, "RetrieveResponseMsg.Results").map((e: any) => {
89 | const name: string = SoapUtils.getStrProp(e, "Name");
90 | const customerKey: string = SoapUtils.getStrProp(e, "CustomerKey");
91 |
92 | return new Asset(
93 | name,
94 | this.getAssetDirectoryName(name, ''),
95 | JSON.stringify(e, null, 2),
96 | directoryUri.connectionId,
97 | [
98 | new AssetFile("rows.csv", "", "", async () => {
99 | const rows = await this.getDataextensionRows(directoryUri.connectionId, customerKey);
100 | return rows !== undefined ? Papa.unparse(rows) : "";
101 | }),
102 | new AssetFile("rows.json", "", "", async () => {
103 | const rows = await this.getDataextensionRows(directoryUri.connectionId, customerKey);
104 | return JSON.stringify(rows, null, 2);
105 | }),
106 | new AssetFile("_columns.readonly.json", "", "", async () => {
107 | const columns = await this.getDataextensionColumns(directoryUri.connectionId, customerKey);
108 | return JSON.stringify(columns, null, 2);
109 | }),
110 | new AssetFile("_docs.readonly.txt", "", "", async () => {
111 | const columns = await this.getDataextensionColumns(directoryUri.connectionId, customerKey);
112 | return this.getDocumentationFileContent(name, columns);
113 | })
114 | ]
115 | );
116 | });
117 | }
118 | } as SoapRequestConfig);
119 |
120 | if (assets === undefined) return [];
121 |
122 | for (const asset of assets) {
123 | this.assetsCache.set(directoryUri.getChildPath(asset.directoryName), asset);
124 | }
125 |
126 | return assets;
127 | }
128 |
129 | async setAssetFile(asset: Asset, file: AssetFile): Promise {
130 | const data = JSON.parse(asset.content);
131 | const customerKey = data?.CustomerKey?.[0];
132 |
133 | if (file.name === "rows.csv") {
134 | const rows = Papa.parse(file.content, {
135 | header: true
136 | }).data;
137 | await this.upsertDataextensionRows(asset.connectionId, customerKey, rows);
138 | }
139 |
140 | else if (file.name === "rows.json") {
141 | const rows = JSON.parse(file.content);
142 | await this.upsertDataextensionRows(asset.connectionId, customerKey, rows);
143 | }
144 |
145 | return;
146 | }
147 |
148 | async saveAsset(asset: Asset): Promise {
149 | return;
150 | }
151 |
152 |
153 |
154 | /* Support methods */
155 |
156 | private getDocumentationFileContent(name: string, columns: any) {
157 | let content = `Dataextension name: ${name}\r\n\r\n`;
158 |
159 | content += '| Name | Type | Not NULL | PK | Default Value |\r\n';
160 | content += '| -------------------- | --------------- | -------- | -------- | --------------- |\r\n';
161 |
162 | columns.forEach((c: any) => {
163 | let type = c.FieldType;
164 |
165 | if (c.FieldType == 'Decimal' && c.MaxLength) {
166 | type += '(' + c.MaxLength + (c.Scale ? ',' + c.Scale : '') + ')';
167 | }
168 |
169 | if (c.FieldType == 'Text' && c.MaxLength) {
170 | type += '(' + c.MaxLength + ')';
171 | }
172 |
173 | content += '| ' + c.Name.padEnd(20, ' ')
174 | + ' | ' + type.padEnd(15, ' ')
175 | + ' | ' + c.IsRequired.padEnd(8, ' ')
176 | + ' | ' + c.IsPrimaryKey.padEnd(8, ' ')
177 | + ' | ' + c.DefaultValue.padEnd(15, ' ')
178 | + ' |\r\n';
179 | })
180 |
181 | return content;
182 | }
183 |
184 | public async customActionFilter(fmUri: FolderManagerUri, content: string): Promise {
185 | const assetUri = fmUri.isAsset ? fmUri : fmUri.parent;
186 |
187 | if (assetUri === undefined) {
188 | return "";
189 | }
190 |
191 | const filterString = await vscode.window.showInputBox({
192 | value: this.filterString,
193 | ignoreFocusOut: true,
194 | placeHolder: "Your filter query string. EG: OrderID = 'ORD2123F2' AND SubscriberKey = 'ABC'"
195 | });
196 |
197 | this.filterString = filterString || "";
198 |
199 | const filter = new SoapFilterExpression(filterString as string).filter;
200 | const asset = await this.getAsset(assetUri, false);
201 | const customerKey = SoapUtils.getStrProp(JSON.parse(asset.content), "CustomerKey");
202 | const rows = await this.getDataextensionRows(assetUri.connectionId, customerKey, filter);
203 |
204 | if (!rows?.length) {
205 | Utils.getInstance().showErrorMessage(new Error(`There are no data rows that match your filter: '${filterString}'. Non-filtered content will be shown`));
206 | return undefined;
207 | }
208 |
209 | Utils.getInstance().showInformationMessage("Filter applied");
210 |
211 | return fmUri.name.endsWith(".csv") ? Papa.unparse(rows) : JSON.stringify(rows, null, 2)
212 | }
213 |
214 | private async upsertDataextensionRows(connectionId: string, customerKey: string, rows: Array): Promise {
215 | const soapRows: Array = rows.map(row => {
216 | const o = {
217 | CustomerKey: customerKey,
218 | Properties: {
219 | Property: new Array()
220 | }
221 | };
222 |
223 | for (const col in row) {
224 | if (col.toLowerCase() !== '_customobjectkey') {
225 | o.Properties.Property.push({
226 | Name: col,
227 | Value: row[col]
228 | });
229 | }
230 | }
231 |
232 | return o;
233 | });
234 |
235 | return ConnectionController.getInstance().soapRequest(connectionId, {
236 | operation: SoapOperation.UPDATE,
237 | body: SoapUtils.createUpdateBody(
238 | "DataExtensionObject",
239 | soapRows
240 | )
241 | });
242 | }
243 |
244 | private async getDataextensionRows(connectionId: string, customerKey: string, filter?: any): Promise> {
245 | const columns = await this.getDataextensionColumns(connectionId, customerKey);
246 |
247 | return ConnectionController.getInstance().soapRequest(connectionId, {
248 | operation: SoapOperation.RETRIEVE,
249 | body: SoapUtils.createRetrieveBody(
250 | `DataExtensionObject[${customerKey}]`,
251 | [
252 | "_CustomObjectKey",
253 | ...columns.map(c => c.Name.trim())
254 | ],
255 | filter
256 | ),
257 | transformResponse: (body) => {
258 | return SoapUtils.getArrProp(body, "RetrieveResponseMsg.Results").map((e: any) => {
259 | const row: any = {};
260 |
261 | SoapUtils.getArrProp(e, "Properties.Property").forEach((c: any) => {
262 | row[SoapUtils.getStrProp(c, "Name")] = SoapUtils.getStrProp(c, "Value");
263 | });
264 |
265 | return row;
266 | });
267 | }
268 | } as SoapRequestConfig);
269 | }
270 |
271 | private async getDataextensionColumns(connectionId: string, customerKey: string): Promise> {
272 | const columns = await ConnectionController.getInstance().soapRequest(connectionId, {
273 | operation: SoapOperation.RETRIEVE,
274 | body: SoapUtils.createRetrieveBody(
275 | "DataExtensionField",
276 | [
277 | "Name",
278 | "FieldType",
279 | "ObjectID",
280 | "MaxLength",
281 | "Scale",
282 | "IsRequired",
283 | "IsPrimaryKey",
284 | "DefaultValue",
285 | "Ordinal"
286 | ],
287 | {
288 | Property: "DataExtension.CustomerKey",
289 | SimpleOperator: "equals",
290 | Value: customerKey
291 | }
292 | ),
293 | transformResponse: (body) => {
294 | return SoapUtils.getArrProp(body, "RetrieveResponseMsg.Results").map((e: any) => {
295 | return {
296 | Name: SoapUtils.getStrProp(e, "Name"),
297 | FieldType: SoapUtils.getStrProp(e, "FieldType"),
298 | ObjectID: SoapUtils.getStrProp(e, "ObjectID"),
299 | MaxLength: SoapUtils.getStrProp(e, "MaxLength"),
300 | Scale: SoapUtils.getStrProp(e, "Scale"),
301 | IsRequired: SoapUtils.getStrProp(e, "IsRequired"),
302 | IsPrimaryKey: SoapUtils.getStrProp(e, "IsPrimaryKey"),
303 | DefaultValue: SoapUtils.getStrProp(e, "DefaultValue"),
304 | Ordinal: parseInt(SoapUtils.getStrProp(e, "Ordinal") || "0")
305 | } as DataextensionColumn;
306 | });
307 | }
308 | } as SoapRequestConfig);
309 |
310 | return columns.sort((a: DataextensionColumn, b: DataextensionColumn) => { return a.Ordinal - b.Ordinal });
311 | }
312 |
313 | private async getDirectoryId(uri: FolderManagerUri): Promise {
314 | if (this.directoriesCache.get(uri.globalPath) !== undefined) {
315 | return this.directoriesCache.get(uri.globalPath) || 0;
316 | }
317 |
318 | if (uri.localPath === '') {
319 | return this.getRootDirectoryId(uri);
320 | }
321 |
322 | if (uri.parent !== undefined) {
323 | const parentDirectoryId = await this.getDirectoryId(uri.parent);
324 | const subdirectories: Array = await this.getSubdirectoriesByDirectoryId(uri, parentDirectoryId);
325 |
326 | for (const d of subdirectories) {
327 | if (d.name === uri.name) {
328 | return d.id;
329 | }
330 | }
331 | }
332 |
333 | throw new Error(`Path not found: ${uri.globalPath}`);
334 | }
335 |
336 | private async findDirectories(uri: FolderManagerUri, filter: any): Promise> {
337 | const hasTokenScopes = await ConnectionController.getInstance().hasTokenRequiredScopes(
338 | uri.connectionId,
339 | ['data_extensions_read', 'data_extensions_write']
340 | );
341 |
342 | if (!hasTokenScopes) {
343 | Utils.getInstance().sendTelemetryEvent("error.manager-dataextensions.missing_api_scope", true, true);
344 | throw new Error('Additional permissions are required for this function: DATA => Data Extensions (Read, Write). Please update your MC installed package and restart VSCode');
345 | }
346 |
347 | const data = await ConnectionController.getInstance().soapRequest(uri.connectionId, {
348 | operation: SoapOperation.RETRIEVE,
349 | body: SoapUtils.createRetrieveBody(
350 | "DataFolder",
351 | ["ID", "Name", "ParentFolder.ID"],
352 | filter
353 | ),
354 | transformResponse: (body) => {
355 | return SoapUtils.getArrProp(body, "RetrieveResponseMsg.Results").map((e: any) => {
356 | return {
357 | id: parseInt(SoapUtils.getStrProp(e, "ID")),
358 | parentId: parseInt(SoapUtils.getStrProp(e, "ParentFolder.ID")),
359 | name: SoapUtils.getStrProp(e, "Name")
360 | } as Directory
361 | });
362 | }
363 | } as SoapRequestConfig);
364 |
365 | if (data === undefined) return [];
366 |
367 | return data as Array
368 | }
369 |
370 | private async getSubdirectoriesByDirectoryId(uri: FolderManagerUri, directoryId: number): Promise> {
371 | const data = await this.findDirectories(uri, {
372 | LeftOperand: {
373 | Property: "ParentFolder.ID",
374 | SimpleOperator: "equals",
375 | Value: directoryId
376 | },
377 | LogicalOperator: "AND",
378 | RightOperand: {
379 | Property: "ContentType",
380 | SimpleOperator: "equals",
381 | Value: this.folderContentType
382 | }
383 | });
384 |
385 | if (directoryId !== 0) {
386 | for (const d of data as Array) {
387 | this.directoriesCache.set(uri.getChildPath(d.name), d.id);
388 | }
389 | }
390 |
391 | return data;
392 | }
393 |
394 | private async getRootDirectoryId(uri: FolderManagerUri): Promise {
395 | if (this.directoriesCache.get(uri.mountPath) !== undefined) {
396 | return this.directoriesCache.get(uri.mountPath) || 0;
397 | }
398 |
399 | const subdirectories: Array = await this.findDirectories(uri, {
400 | LeftOperand: {
401 | Property: "CustomerKey",
402 | SimpleOperator: "equals",
403 | Value: this.rootFolderCustomerKey
404 | },
405 | LogicalOperator: "AND",
406 | RightOperand: {
407 | Property: "ContentType",
408 | SimpleOperator: "equals",
409 | Value: this.folderContentType
410 | }
411 | });
412 |
413 | /** Access to shared DEs in only possible through the Ent BU */
414 | if(this.folderContentType == "shared_dataextension" && subdirectories && subdirectories.length == 0){
415 | throw new Error("Connect to the Enterprise Business Unit (top level) to get access to shared Dataextensions");
416 | }
417 |
418 | if(!subdirectories || subdirectories.length == 0){
419 | throw new Error("Can't find the root Dataextensions folder");
420 | }
421 |
422 | this.directoriesCache.set(uri.globalPath, subdirectories[0].id);
423 |
424 | return subdirectories[0].id;
425 | }
426 | }
427 |
--------------------------------------------------------------------------------
/connection-manager/js/app.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"js/app.js","mappings":"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","sources":["webpack://mcfs-connection-manager/./src/App.vue?83ed","webpack://mcfs-connection-manager/./src/components/ConnectionList.vue?3056","webpack://mcfs-connection-manager/src/components/ConnectionList.vue","webpack://mcfs-connection-manager/./src/components/ConnectionList.vue?728c","webpack://mcfs-connection-manager/./src/components/ConnectionList.vue","webpack://mcfs-connection-manager/src/App.vue","webpack://mcfs-connection-manager/./src/App.vue?facb","webpack://mcfs-connection-manager/./src/App.vue","webpack://mcfs-connection-manager/./src/main.js","webpack://mcfs-connection-manager/webpack/bootstrap","webpack://mcfs-connection-manager/webpack/runtime/chunk loaded","webpack://mcfs-connection-manager/webpack/runtime/define property getters","webpack://mcfs-connection-manager/webpack/runtime/global","webpack://mcfs-connection-manager/webpack/runtime/hasOwnProperty shorthand","webpack://mcfs-connection-manager/webpack/runtime/jsonp chunk loading","webpack://mcfs-connection-manager/webpack/startup"],"sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{attrs:{\"id\":\"app\"}},[_c('ConnectionList',{attrs:{\"connections\":_vm.connections},on:{\"connect\":function($event){return _vm.connect($event)},\"save\":function($event){return _vm.save($event)}}}),_c('Help')],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"hello\"},[_c('h2',[_vm._v(\"Connections list\")]),_c('div',{staticStyle:{\"margin-bottom\":\"10px\"}},[_c('button',{on:{\"click\":function($event){return _vm.add()}}},[_vm._v(\"NEW CONNECTION\")]),_c('button',{on:{\"click\":function($event){return _vm.save()}}},[_vm._v(\"SAVE CHANGES\")])]),_c('table',{staticClass:\"connections\",attrs:{\"cellpadding\":\"0\",\"cellspacing\":\"0\",\"border\":\"0\"}},[_vm._m(0),_c('tbody',_vm._l((this.localConnections),function(c,i){return _c('tr',{key:i},[_c('td',[_c('button',{ref:\"btn_connect\",refInFor:true,on:{\"click\":function($event){return _vm.connect(c, i)}}},[_vm._v(\"CONNECT\")])]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.name),expression:\"c.name\"}],attrs:{\"type\":\"text\",\"placeholder\":\"connection name\"},domProps:{\"value\":(c.name)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"name\", $event.target.value)}}})]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.account_id),expression:\"c.account_id\"}],attrs:{\"type\":\"text\",\"placeholder\":\"business unit id\"},domProps:{\"value\":(c.account_id)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"account_id\", $event.target.value)}}})]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.authBaseUri),expression:\"c.authBaseUri\"}],attrs:{\"type\":\"text\",\"placeholder\":\"api auth base uri\"},domProps:{\"value\":(c.authBaseUri)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"authBaseUri\", $event.target.value)}}})]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.client_id),expression:\"c.client_id\"}],attrs:{\"type\":\"password\",\"placeholder\":\"client id\"},domProps:{\"value\":(c.client_id)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"client_id\", $event.target.value)}}})]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.client_secret),expression:\"c.client_secret\"}],attrs:{\"type\":\"password\",\"placeholder\":\"client secret\"},domProps:{\"value\":(c.client_secret)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"client_secret\", $event.target.value)}}})]),_c('td',[_c('button',{staticClass:\"delete\",on:{\"click\":function($event){return _vm.remove(i)}}},[_vm._v(\"✕\")])])])}),0)])])}\nvar staticRenderFns = [function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('thead',[_c('tr',[_c('th',{attrs:{\"width\":\"100\"}}),_c('th',{attrs:{\"width\":\"100\"}},[_vm._v(\"Label\")]),_c('th',{attrs:{\"width\":\"100\"}},[_vm._v(\"MID\")]),_c('th',[_vm._v(\"Auth Base Uri\")]),_c('th',{attrs:{\"width\":\"200\"}},[_vm._v(\"Client ID\")]),_c('th',{attrs:{\"width\":\"200\"}},[_vm._v(\"Client Secret\")]),_c('th',{attrs:{\"width\":\"50§\"}})])])}]\n\nexport { render, staticRenderFns }","\n\t
\n\t\t
Connections list
\n\n\t\t
\n\t\t\t\n\t\t\t\n\t\t
\n\n\t\t
\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
Label
\n\t\t\t\t\t
MID
\n\t\t\t\t\t
Auth Base Uri
\n\t\t\t\t\t
Client ID
\n\t\t\t\t\t
Client Secret
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t
\n\n\n\n\n\n\n","import mod from \"-!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40[0].rules[0].use[1]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./ConnectionList.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40[0].rules[0].use[1]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./ConnectionList.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./ConnectionList.vue?vue&type=template&id=e7f38064&scoped=true&\"\nimport script from \"./ConnectionList.vue?vue&type=script&lang=js&\"\nexport * from \"./ConnectionList.vue?vue&type=script&lang=js&\"\nimport style0 from \"./ConnectionList.vue?vue&type=style&index=0&id=e7f38064&scoped=true&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"e7f38064\",\n null\n \n)\n\nexport default component.exports","\n\t
\n\t\t\n\t\t\n\t
\n\n\n\n\n\n","import mod from \"-!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js??clonedRuleSet-40[0].rules[0].use[1]!../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./App.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js??clonedRuleSet-40[0].rules[0].use[1]!../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./App.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./App.vue?vue&type=template&id=bf783f4c&\"\nimport script from \"./App.vue?vue&type=script&lang=js&\"\nexport * from \"./App.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import Vue from 'vue'\nimport App from './App.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n\trender: h => h(App),\n}).$mount('#app')","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t143: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunkmcfs_connection_manager\"] = self[\"webpackChunkmcfs_connection_manager\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [998], function() { return __webpack_require__(762); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["render","_vm","this","_h","$createElement","_c","_self","attrs","connections","on","$event","connect","save","staticRenderFns","staticClass","_v","staticStyle","add","_m","_l","c","i","key","ref","refInFor","directives","name","rawName","value","expression","domProps","hasChanges","target","composing","$set","remove","props","data","localConnections","methods","account_id","authBaseUri","client_id","client_secret","setTimeout","watch","components","component","vscode","action","content","onMessageReceived","mounted","postMessage","console","window","ConnectionList","Vue","h","App","$mount","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","m","deferred","O","result","chunkIds","fn","priority","notFulfilled","Infinity","length","fulfilled","j","Object","keys","every","splice","r","d","definition","o","defineProperty","enumerable","get","g","globalThis","Function","e","obj","prop","prototype","hasOwnProperty","call","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","id","chunkLoadingGlobal","self","forEach","bind","push","__webpack_exports__"],"sourceRoot":""}
--------------------------------------------------------------------------------
/connection-manager/js/app-legacy.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"js/app-legacy.js","mappings":"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","sources":["webpack://mcfs-connection-manager/./src/App.vue?83ed","webpack://mcfs-connection-manager/./src/components/ConnectionList.vue?3056","webpack://mcfs-connection-manager/src/components/ConnectionList.vue","webpack://mcfs-connection-manager/./src/components/ConnectionList.vue?728c","webpack://mcfs-connection-manager/./src/components/ConnectionList.vue","webpack://mcfs-connection-manager/src/App.vue","webpack://mcfs-connection-manager/./src/App.vue?facb","webpack://mcfs-connection-manager/./src/App.vue","webpack://mcfs-connection-manager/./src/main.js","webpack://mcfs-connection-manager/webpack/bootstrap","webpack://mcfs-connection-manager/webpack/runtime/chunk loaded","webpack://mcfs-connection-manager/webpack/runtime/define property getters","webpack://mcfs-connection-manager/webpack/runtime/global","webpack://mcfs-connection-manager/webpack/runtime/hasOwnProperty shorthand","webpack://mcfs-connection-manager/webpack/runtime/jsonp chunk loading","webpack://mcfs-connection-manager/webpack/startup"],"sourcesContent":["var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{attrs:{\"id\":\"app\"}},[_c('ConnectionList',{attrs:{\"connections\":_vm.connections},on:{\"connect\":function($event){return _vm.connect($event)},\"save\":function($event){return _vm.save($event)}}}),_c('Help')],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:\"hello\"},[_c('h2',[_vm._v(\"Connections list\")]),_c('div',{staticStyle:{\"margin-bottom\":\"10px\"}},[_c('button',{on:{\"click\":function($event){return _vm.add()}}},[_vm._v(\"NEW CONNECTION\")]),_c('button',{on:{\"click\":function($event){return _vm.save()}}},[_vm._v(\"SAVE CHANGES\")])]),_c('table',{staticClass:\"connections\",attrs:{\"cellpadding\":\"0\",\"cellspacing\":\"0\",\"border\":\"0\"}},[_vm._m(0),_c('tbody',_vm._l((this.localConnections),function(c,i){return _c('tr',{key:i},[_c('td',[_c('button',{ref:\"btn_connect\",refInFor:true,on:{\"click\":function($event){return _vm.connect(c, i)}}},[_vm._v(\"CONNECT\")])]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.name),expression:\"c.name\"}],attrs:{\"type\":\"text\",\"placeholder\":\"connection name\"},domProps:{\"value\":(c.name)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"name\", $event.target.value)}}})]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.account_id),expression:\"c.account_id\"}],attrs:{\"type\":\"text\",\"placeholder\":\"business unit id\"},domProps:{\"value\":(c.account_id)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"account_id\", $event.target.value)}}})]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.authBaseUri),expression:\"c.authBaseUri\"}],attrs:{\"type\":\"text\",\"placeholder\":\"api auth base uri\"},domProps:{\"value\":(c.authBaseUri)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"authBaseUri\", $event.target.value)}}})]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.client_id),expression:\"c.client_id\"}],attrs:{\"type\":\"password\",\"placeholder\":\"client id\"},domProps:{\"value\":(c.client_id)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"client_id\", $event.target.value)}}})]),_c('td',[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(c.client_secret),expression:\"c.client_secret\"}],attrs:{\"type\":\"password\",\"placeholder\":\"client secret\"},domProps:{\"value\":(c.client_secret)},on:{\"change\":function($event){_vm.hasChanges = true},\"input\":function($event){if($event.target.composing){ return; }_vm.$set(c, \"client_secret\", $event.target.value)}}})]),_c('td',[_c('button',{staticClass:\"delete\",on:{\"click\":function($event){return _vm.remove(i)}}},[_vm._v(\"✕\")])])])}),0)])])}\nvar staticRenderFns = [function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('thead',[_c('tr',[_c('th',{attrs:{\"width\":\"100\"}}),_c('th',{attrs:{\"width\":\"100\"}},[_vm._v(\"Label\")]),_c('th',{attrs:{\"width\":\"100\"}},[_vm._v(\"MID\")]),_c('th',[_vm._v(\"Auth Base Uri\")]),_c('th',{attrs:{\"width\":\"200\"}},[_vm._v(\"Client ID\")]),_c('th',{attrs:{\"width\":\"200\"}},[_vm._v(\"Client Secret\")]),_c('th',{attrs:{\"width\":\"50§\"}})])])}]\n\nexport { render, staticRenderFns }","\n\t
\n\t\t
Connections list
\n\n\t\t
\n\t\t\t\n\t\t\t\n\t\t
\n\n\t\t
\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
Label
\n\t\t\t\t\t
MID
\n\t\t\t\t\t
Auth Base Uri
\n\t\t\t\t\t
Client ID
\n\t\t\t\t\t
Client Secret
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t
\n\n\n\n\n\n\n","import mod from \"-!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40[0].rules[0].use[1]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./ConnectionList.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../node_modules/thread-loader/dist/cjs.js!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40[0].rules[0].use[1]!../../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./ConnectionList.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./ConnectionList.vue?vue&type=template&id=e7f38064&scoped=true&\"\nimport script from \"./ConnectionList.vue?vue&type=script&lang=js&\"\nexport * from \"./ConnectionList.vue?vue&type=script&lang=js&\"\nimport style0 from \"./ConnectionList.vue?vue&type=style&index=0&id=e7f38064&scoped=true&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"e7f38064\",\n null\n \n)\n\nexport default component.exports","\n\t
\n\t\t\n\t\t\n\t
\n\n\n\n\n\n","import mod from \"-!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js??clonedRuleSet-40[0].rules[0].use[1]!../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./App.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../node_modules/thread-loader/dist/cjs.js!../node_modules/babel-loader/lib/index.js??clonedRuleSet-40[0].rules[0].use[1]!../node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./App.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./App.vue?vue&type=template&id=bf783f4c&\"\nimport script from \"./App.vue?vue&type=script&lang=js&\"\nexport * from \"./App.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import Vue from 'vue'\nimport App from './App.vue'\n\nVue.config.productionTip = false\n\nnew Vue({\n\trender: h => h(App),\n}).$mount('#app')","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// no baseURI\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t143: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunkmcfs_connection_manager\"] = self[\"webpackChunkmcfs_connection_manager\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [998], function() { return __webpack_require__(3762); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["render","_vm","this","_h","$createElement","_c","_self","attrs","connections","on","$event","connect","save","staticRenderFns","staticClass","_v","staticStyle","add","_m","_l","c","i","key","ref","refInFor","directives","name","rawName","value","expression","domProps","hasChanges","target","composing","$set","remove","props","data","localConnections","methods","account_id","authBaseUri","client_id","client_secret","setTimeout","watch","components","component","vscode","action","content","onMessageReceived","mounted","postMessage","console","window","ConnectionList","Vue","h","App","$mount","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","m","deferred","O","result","chunkIds","fn","priority","notFulfilled","Infinity","length","fulfilled","j","Object","keys","every","splice","r","d","definition","o","defineProperty","enumerable","get","g","globalThis","Function","e","obj","prop","prototype","hasOwnProperty","call","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","id","chunkLoadingGlobal","self","forEach","bind","push","__webpack_exports__"],"sourceRoot":""}
--------------------------------------------------------------------------------