Adding SQL Edge project template (#13558)

* Checkpoint

* removing flag for not creating subfolder

* Adding Edge template

* Removing janky map function

* Adding templates for additional objects

* Updating tests, fixing bug

* Added Edge project icon

* Updating strings to Drew-approved text

* Cleaning up template scripts and Edge project template names
This commit is contained in:
Benjin Dubishar
2020-12-03 10:33:31 -08:00
committed by GitHub
parent 08735c9434
commit fde031be48
15 changed files with 230 additions and 67 deletions

View File

@@ -56,40 +56,35 @@ export class ProjectsController {
* @param folderUri
* @param projectGuid
*/
public async createNewProject(newProjName: string, folderUri: vscode.Uri, makeOwnFolder: boolean, projectGuid?: string): Promise<string> {
if (projectGuid && !UUID.isUUID(projectGuid)) {
throw new Error(`Specified GUID is invalid: '${projectGuid}'`);
public async createNewProject(creationParams: NewProjectParams): Promise<string> {
if (creationParams.projectGuid && !UUID.isUUID(creationParams.projectGuid)) {
throw new Error(`Specified GUID is invalid: '${creationParams.projectGuid}'`);
}
const macroDict: Record<string, string> = {
'PROJECT_NAME': newProjName,
'PROJECT_GUID': projectGuid ?? UUID.generateUuid().toUpperCase()
'PROJECT_NAME': creationParams.newProjName,
'PROJECT_GUID': creationParams.projectGuid ?? UUID.generateUuid().toUpperCase()
};
let newProjFileContents = this.macroExpansion(templates.newSqlProjectTemplate, macroDict);
let newProjFileContents = templates.macroExpansion(templates.newSqlProjectTemplate, macroDict);
let newProjFileName = newProjName;
let newProjFileName = creationParams.newProjName;
if (!newProjFileName.toLowerCase().endsWith(constants.sqlprojExtension)) {
newProjFileName += constants.sqlprojExtension;
}
const newProjFilePath = makeOwnFolder ? path.join(folderUri.fsPath, path.parse(newProjFileName).name, newProjFileName) : path.join(folderUri.fsPath, newProjFileName);
const newProjFilePath = path.join(creationParams.folderUri.fsPath, path.parse(newProjFileName).name, newProjFileName);
let fileExists = false;
try {
await fs.access(newProjFilePath);
fileExists = true;
}
catch { } // file doesn't already exist
if (fileExists) {
if (await utils.exists(newProjFilePath)) {
throw new Error(constants.projectAlreadyExists(newProjFileName, path.parse(newProjFilePath).dir));
}
await fs.mkdir(path.dirname(newProjFilePath), { recursive: true });
await fs.writeFile(newProjFilePath, newProjFileContents);
await this.addTemplateFiles(newProjFilePath, creationParams.projectTypeId);
return newProjFilePath;
}
@@ -256,7 +251,7 @@ export class ProjectsController {
}
}
const itemType = templates.projectScriptTypeMap()[itemTypeName.toLocaleLowerCase()];
const itemType = templates.get(itemTypeName);
const absolutePathToParent = path.join(project.projectFolderPath, relativePath);
let itemObjectName = await this.promptForNewObjectName(itemType, project, absolutePathToParent, constants.sqlFileExtension);
@@ -266,18 +261,10 @@ export class ProjectsController {
return; // user cancelled
}
const newFileText = this.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName });
const newFileText = templates.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName });
const relativeFilePath = path.join(relativePath, itemObjectName + constants.sqlFileExtension);
try {
// check if file already exists
const absoluteFilePath = path.join(project.projectFolderPath, relativeFilePath);
const fileExists = await utils.exists(absoluteFilePath);
if (fileExists) {
throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name));
}
const newEntry = await project.addScriptItem(relativeFilePath, newFileText, itemType.type);
await vscode.commands.executeCommand(constants.vscodeOpenCommand, newEntry.fsUri);
@@ -460,6 +447,12 @@ export class ProjectsController {
return addDatabaseReferenceDialog;
}
/**
* Adds a database reference to a project, after selections have been made in the dialog
* @param project project to which to add the database reference
* @param settings settings for the database reference
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/
public async addDatabaseReferenceCallback(project: Project, settings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings | IProjectReferenceSettings, context: dataworkspace.WorkspaceTreeItem): Promise<void> {
try {
if ((<IProjectReferenceSettings>settings).projectName !== undefined) {
@@ -494,6 +487,11 @@ export class ProjectsController {
}
}
/**
* Validates the contents of an external streaming job's query against the last-built dacpac.
* If no dacpac exists at the output path, one will be built first.
* @param node a treeItem in a project's hierarchy, to be used to obtain a Project
*/
public async validateExternalStreamingJob(node: dataworkspace.WorkspaceTreeItem): Promise<mssql.ValidateStreamingJobResult> {
const project: Project = this.getProjectFromContext(node);
@@ -547,6 +545,29 @@ export class ProjectsController {
}
}
private async addTemplateFiles(newProjFilePath: string, projectTypeId: string): Promise<void> {
if (projectTypeId === constants.emptySqlDatabaseProjectTypeId || newProjFilePath === '') {
return;
}
if (projectTypeId === constants.edgeSqlDatabaseProjectTypeId) {
const project = await Project.openProject(newProjFilePath);
await this.createFileFromTemplate(project, templates.get(templates.table), 'DataTable.sql', { 'OBJECT_NAME': 'DataTable' });
await this.createFileFromTemplate(project, templates.get(templates.dataSource), 'EdgeHubInputDataSource.sql', { 'OBJECT_NAME': 'EdgeHubInputDataSource', 'LOCATION': 'edgehub://' });
await this.createFileFromTemplate(project, templates.get(templates.dataSource), 'SqlOutputDataSource.sql', { 'OBJECT_NAME': 'SqlOutputDataSource', 'LOCATION': 'sqlserver://tcp:.,1433' });
await this.createFileFromTemplate(project, templates.get(templates.fileFormat), 'StreamFileFormat.sql', { 'OBJECT_NAME': 'StreamFileFormat' });
await this.createFileFromTemplate(project, templates.get(templates.externalStream), 'EdgeHubInputStream.sql', { 'OBJECT_NAME': 'EdgeHubInputStream', 'DATA_SOURCE_NAME': 'EdgeHubInputDataSource', 'LOCATION': 'input', 'OPTIONS': ',\n\tFILE_FORMAT = StreamFileFormat' });
await this.createFileFromTemplate(project, templates.get(templates.externalStream), 'SqlOutputStream.sql', { 'OBJECT_NAME': 'SqlOutputStream', 'DATA_SOURCE_NAME': 'SqlOutputDataSource', 'LOCATION': 'TSQLStreaming.dbo.DataTable', 'OPTIONS': '' });
await this.createFileFromTemplate(project, templates.get(templates.externalStreamingJob), 'EdgeStreamingJob.sql', { 'OBJECT_NAME': 'EdgeStreamingJob' });
}
}
private async createFileFromTemplate(project: Project, itemType: templates.ProjectScriptType, relativePath: string, expansionMacros: Record<string, string>): Promise<void> {
const newFileText = templates.macroExpansion(itemType.templateScript, expansionMacros);
await project.addScriptItem(relativePath, newFileText, itemType.type);
}
private getProjectFromContext(context: Project | BaseProjectTreeItem | dataworkspace.WorkspaceTreeItem): Project {
if ('element' in context) {
return context.element.root.project;
@@ -570,21 +591,7 @@ export class ProjectsController {
return (ext.exports as mssql.IExtension).dacFx;
}
private macroExpansion(template: string, macroDict: Record<string, string>): string {
const macroIndicator = '@@';
let output = template;
for (const macro in macroDict) {
// check if value contains the macroIndicator, which could break expansion for successive macros
if (macroDict[macro].includes(macroIndicator)) {
throw new Error(`Macro value ${macroDict[macro]} is invalid because it contains ${macroIndicator}`);
}
output = output.replace(new RegExp(macroIndicator + macro + macroIndicator, 'g'), macroDict[macro]);
}
return output;
}
private async promptForNewObjectName(itemType: templates.ProjectScriptType, _project: Project, folderPath: string, fileExtension?: string): Promise<string | undefined> {
const suggestedName = itemType.friendlyName.replace(/\s+/g, '');
@@ -632,7 +639,12 @@ export class ProjectsController {
const newProjFolderUri = model.filePath;
const newProjFilePath = await this.createNewProject(model.projName, vscode.Uri.file(newProjFolderUri), true);
const newProjFilePath = await this.createNewProject({
newProjName: model.projName,
folderUri: vscode.Uri.file(newProjFolderUri),
projectTypeId: constants.emptySqlDatabaseProjectTypeId
});
model.filePath = path.dirname(newProjFilePath);
this.setFilePath(model);
@@ -718,3 +730,10 @@ export class ProjectsController {
//#endregion
}
export interface NewProjectParams {
newProjName: string;
folderUri: vscode.Uri;
projectTypeId: string;
projectGuid?: string;
}