diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/AgentService.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/AgentService.cs index 1b9026ff..a319bb81 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/AgentService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/AgentService.cs @@ -13,7 +13,6 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo.Agent; using Microsoft.SqlTools.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Agent.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Hosting; @@ -122,6 +121,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent this.ServiceHost.SetRequestHandler(UpdateAgentScheduleRequest.Type, HandleUpdateAgentScheduleRequest); this.ServiceHost.SetRequestHandler(DeleteAgentScheduleRequest.Type, HandleDeleteAgentScheduleRequest); + // Notebook request handlers + this.ServiceHost.SetRequestHandler(AgentNotebooksRequest.Type, HandleAgentNotebooksRequest); + this.ServiceHost.SetRequestHandler(AgentNotebookHistoryRequest.Type, HandleAgentNotebookHistoryRequest); + this.ServiceHost.SetRequestHandler(AgentNotebookMaterializedRequest.Type, HandleAgentNotebookMaterializedRequest); + this.ServiceHost.SetRequestHandler(AgentNotebookTemplateRequest.Type, HandleAgentNotebookTemplateRequest); + this.ServiceHost.SetRequestHandler(CreateAgentNotebookRequest.Type, HandleCreateAgentNotebookRequest); + this.ServiceHost.SetRequestHandler(DeleteAgentNotebookRequest.Type, HandleDeleteAgentNotebooksRequest); + this.ServiceHost.SetRequestHandler(UpdateAgentNotebookRequest.Type, HandleUpdateAgentNotebookRequest); + + + serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) => + { + DeleteAgentNotebooksTempFiles(); + await Task.FromResult(0); + }); + } #region "Jobs Handlers" @@ -191,7 +206,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent SqlConnectionInfo sqlConnInfo = tuple.Item1; DataTable dt = tuple.Item2; ServerConnection connection = tuple.Item3; - + // Send Steps, Alerts and Schedules with job history in background // Add steps to the job if any JobStepCollection steps = jobs[parameters.JobName].JobSteps; @@ -271,7 +286,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent var serverConnection = ConnectionService.OpenServerConnection(connInfo); var jobHelper = new JobHelper(serverConnection); jobHelper.JobName = parameters.JobName; - switch(parameters.Action) + switch (parameters.Action) { case "run": jobHelper.Start(); @@ -475,7 +490,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent IsEnabled = alert.IsEnabled, JobId = alert.JobID.ToString(), JobName = alert.JobName, - LastOccurrenceDate =alert.LastOccurrenceDate.ToString(), + LastOccurrenceDate = alert.LastOccurrenceDate.ToString(), LastResponseDate = alert.LastResponseDate.ToString(), MessageId = alert.MessageID, NotificationMessage = alert.NotificationMessage, @@ -912,12 +927,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent } // Execute step actions if they exist - if (jobInfo.JobSteps != null && jobInfo.JobSteps.Length > 0) + if (configAction != ConfigAction.Drop && jobInfo.JobSteps != null && jobInfo.JobSteps.Length > 0) { foreach (AgentJobStepInfo step in jobInfo.JobSteps) - { + { configAction = ConfigAction.Create; - foreach(JobStep jobStep in dataContainer.Server.JobServer.Jobs[originalJobName].JobSteps) + foreach (JobStep jobStep in dataContainer.Server.JobServer.Jobs[originalJobName].JobSteps) { // any changes made to step other than name or ordering if ((step.StepName == jobStep.Name && step.Id == jobStep.ID) || @@ -928,7 +943,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent { configAction = ConfigAction.Update; break; - } + } } await ConfigureAgentJobStep(ownerUri, step, configAction, runType, jobData, dataContainer); } @@ -1018,9 +1033,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); - } - else - { + } + else + { if (jobData == null) { // If the alert is being created inside a job @@ -1162,7 +1177,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent STParameters param = new STParameters(dataContainer.Document); string originalName = jobInfo != null && !string.Equals(jobName, jobInfo.Name) ? jobName : string.Empty; - param.SetParam("job", configAction == ConfigAction.Update ? jobName : string.Empty); + param.SetParam("job", configAction == ConfigAction.Update ? jobName : string.Empty); param.SetParam("jobid", string.Empty); jobData = new JobData(dataContainer, jobInfo, configAction); @@ -1211,6 +1226,272 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent return new Tuple(sqlConnInfo, dt, serverConnection); } + internal async Task HandleAgentNotebooksRequest(AgentNotebooksParams parameters, RequestContext requestContext) + { + await Task.Run(async () => + { + var result = new AgentNotebooksResult(); + try + { + ConnectionInfo connInfo; + ConnectionServiceInstance.TryFindConnection( + parameters.OwnerUri, + out connInfo); + result.Success = true; + result.Notebooks = AgentNotebookHelper.GetAgentNotebooks(connInfo).Result; + } + catch (Exception e) + { + result.Success = false; + result.ErrorMessage = e.ToString(); + } + await requestContext.SendResult(result); + }); + } + + internal async Task HandleAgentNotebookHistoryRequest( + AgentNotebookHistoryParams parameters, RequestContext requestContext) + { + await Task.Run(async () => + { + var result = new AgentNotebookHistoryResult(); + try + { + + ConnectionInfo connInfo; + ConnectionServiceInstance.TryFindConnection( + parameters.OwnerUri, + out connInfo); + + result = GetAgentNotebookHistories( + connInfo, + parameters.JobId, + parameters.JobName, + parameters.TargetDatabase + ); + + result.Success = true; + } + catch (Exception e) + { + result.Success = false; + result.ErrorMessage = e.ToString(); + } + await requestContext.SendResult(result); + }); + } + + internal async Task HandleAgentNotebookMaterializedRequest(AgentNotebookMaterializedParams parameters, RequestContext requestContext) + { + await Task.Run(async () => + { + var result = new AgentNotebookMaterializedResult(); + try + { + + ConnectionInfo connInfo; + ConnectionServiceInstance.TryFindConnection( + parameters.OwnerUri, + out connInfo); + result.NotebookMaterialized = AgentNotebookHelper.GetMaterializedNotebook(connInfo, parameters.NotebookMaterializedId, parameters.TargetDatabase).Result; + result.Success = true; + } + catch (Exception e) + { + result.Success = false; + result.ErrorMessage = e.ToString(); + + } + await requestContext.SendResult(result); + }); + } + + internal async Task HandleAgentNotebookTemplateRequest(AgentNotebookTemplateParams parameters, RequestContext requestContext) + { + await Task.Run(async () => + { + var result = new AgentNotebookTemplateResult(); + try + { + + ConnectionInfo connInfo; + ConnectionServiceInstance.TryFindConnection( + parameters.OwnerUri, + out connInfo); + result.NotebookTemplate = AgentNotebookHelper.GetTemplateNotebook(connInfo, parameters.JobId, parameters.TargetDatabase).Result; + result.Success = true; + } + catch (Exception e) + { + result.Success = false; + result.ErrorMessage = e.ToString(); + + } + await requestContext.SendResult(result); + }); + } + + internal async Task HandleCreateAgentNotebookRequest(CreateAgentNotebookParams parameters, RequestContext requestContext) + { + await Task.Run(async () => + { + var result = new CreateAgentNotebookResult(); + try + { + // storing result + result.Success = true; + await AgentNotebookHelper.CreateNotebook( + this, + parameters.OwnerUri, + parameters.Notebook, + parameters.TemplateFilePath, + ManagementUtils.asRunType(parameters.TaskExecutionMode) + ); + + } + catch (Exception e) + { + result.Success = false; + result.ErrorMessage = e.ToString(); + } + await requestContext.SendResult(result); + }); + } + + internal async Task HandleDeleteAgentNotebooksRequest(DeleteAgentNotebookParams parameters, RequestContext requestContext) + { + await Task.Run(async () => + { + var result = new ResultStatus(); + try + { + // Calling delete notebook helper function + await AgentNotebookHelper.DeleteNotebook( + this, + parameters.OwnerUri, + parameters.Notebook, + ManagementUtils.asRunType(parameters.TaskExecutionMode) + ); + result.Success = true; + } + catch (Exception e) + { + result.Success = false; + result.ErrorMessage = e.ToString(); + } + await requestContext.SendResult(result); + }); + } + + internal async Task HandleUpdateAgentNotebookRequest(UpdateAgentNotebookParams parameters, RequestContext requestContext) + { + await Task.Run(async () => + { + var result = new UpdateAgentNotebookResult(); + try + { + // Calling update helper function + await AgentNotebookHelper.UpdateNotebook( + this, + parameters.OwnerUri, + parameters.OriginalNotebookName, + parameters.Notebook, + parameters.TemplateFilePath, + ManagementUtils.asRunType(parameters.TaskExecutionMode)); + result.Success = true; + } + catch (Exception e) + { + result.Success = false; + result.ErrorMessage = e.ToString(); + + } + await requestContext.SendResult(result); + }); + } + + public AgentNotebookHistoryResult GetAgentNotebookHistories( + ConnectionInfo connInfo, + string jobId, + string jobName, + string targetDatabase + ) + { + AgentNotebookHistoryResult result = new AgentNotebookHistoryResult(); + // fetching Job information + CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); + var jobServer = dataContainer.Server.JobServer; + var jobs = jobServer.Jobs; + Tuple tuple = CreateSqlConnection(connInfo, jobId); + SqlConnectionInfo sqlConnInfo = tuple.Item1; + DataTable dt = tuple.Item2; + + // add steps to the job if any + JobStepCollection steps = jobs[jobName].JobSteps; + var jobSteps = new List(); + foreach (JobStep step in steps) + { + jobSteps.Add(AgentUtilities.ConvertToAgentJobStepInfo(step, jobId, jobName)); + } + result.Steps = jobSteps.ToArray(); + + // add schedules to the job if any + JobScheduleCollection schedules = jobs[jobName].JobSchedules; + var jobSchedules = new List(); + foreach (JobSchedule schedule in schedules) + { + jobSchedules.Add(AgentUtilities.ConvertToAgentScheduleInfo(schedule)); + } + result.Schedules = jobSchedules.ToArray(); + + // add histories + int count = dt.Rows.Count; + List notebookHistories = new List(); + if (count > 0) + { + var job = dt.Rows[0]; + Guid tempjobId = (Guid)job[AgentUtilities.UrnJobId]; + int runStatus = Convert.ToInt32(job[AgentUtilities.UrnRunStatus], System.Globalization.CultureInfo.InvariantCulture); + var t = new LogSourceJobHistory(jobName, sqlConnInfo, null, runStatus, tempjobId, null); + var tlog = t as ILogSource; + tlog.Initialize(); + var logEntries = t.LogEntries; + var jobHistories = AgentUtilities.ConvertToAgentNotebookHistoryInfo(logEntries, job, steps); + // fetching notebook part of histories + Dictionary notebookHistoriesDict = new Dictionary(); + DataTable materializedNotebookTable = AgentNotebookHelper.GetAgentNotebookHistories(connInfo, jobId, targetDatabase).Result; + foreach (DataRow materializedNotebookRow in materializedNotebookTable.Rows) + { + string materializedRunDateTime = materializedNotebookRow["run_date"].ToString() + materializedNotebookRow["run_time"].ToString(); + notebookHistoriesDict.Add(materializedRunDateTime, materializedNotebookRow); + } + + // adding notebook information to job histories + foreach (var jobHistory in jobHistories) + { + string jobRuntime = jobHistory.RunDate.ToString("yyyyMMddHHmmss"); + AgentNotebookHistoryInfo notebookHistory = jobHistory; + if (notebookHistoriesDict.ContainsKey(jobRuntime)) + { + notebookHistory.MaterializedNotebookId = (int)notebookHistoriesDict[jobRuntime]["materialized_id"]; + notebookHistory.MaterializedNotebookErrorInfo = notebookHistoriesDict[jobRuntime]["notebook_error"] as string; + } + notebookHistories.Add(notebookHistory); + } + result.Histories = notebookHistories.ToArray(); + tlog.CloseReader(); + } + return result; + } + #endregion // "Helpers" + + internal void DeleteAgentNotebooksTempFiles() + { + if (FileUtilities.SafeDirectoryExists(FileUtilities.AgentNotebookTempFolder)) + { + FileUtilities.SafeDirectoryDelete(FileUtilities.AgentNotebookTempFolder, true); + } + } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentUtilities.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentUtilities.cs index 3c727a11..bff57fcb 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentUtilities.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentUtilities.cs @@ -66,6 +66,37 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent }; } + public static AgentNotebookInfo ConvertToAgentNotebookInfo(JobProperties job) + { + return new AgentNotebookInfo(){ + Name = job.Name, + Description = job.Description, + CurrentExecutionStatus = (Contracts.JobExecutionStatus) job.CurrentExecutionStatus, + LastRunOutcome = (Contracts.CompletionResult) job.LastRunOutcome, + CurrentExecutionStep = job.CurrentExecutionStep, + Enabled = job.Enabled, + HasTarget = job.HasTarget, + HasSchedule = job.HasSchedule, + HasStep = job.HasStep, + Runnable = job.Runnable, + Category = job.Category, + CategoryId = job.CategoryID, + CategoryType = job.CategoryType, + LastRun = job.LastRun != null ? job.LastRun.ToString() : string.Empty, + NextRun = job.NextRun != null ? job.NextRun.ToString() : string.Empty, + JobId = job.JobID != null ? job.JobID.ToString() : null, + OperatorToEmail = job.OperatorToEmail, + OperatorToPage = job.OperatorToPage, + StartStepId = job.StartStepID, + EmailLevel = job.EmailLevel, + PageLevel = job.PageLevel, + EventLogLevel = job.EventLogLevel, + DeleteLevel = job.DeleteLevel, + Owner = job.Owner + }; + + } + internal static AgentJobStep ConvertToAgentJobStep(JobStep step, LogSourceJobHistory.LogEntryJobHistory logEntry, string jobId) { AgentJobStepInfo stepInfo = new AgentJobStepInfo(); @@ -120,6 +151,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent stepInfo.ProxyName = step.ProxyName; return stepInfo; } + internal static AgentScheduleInfo ConvertToAgentScheduleInfo(JobSchedule schedule) { @@ -221,5 +253,47 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent } return jobs; } + + public static List ConvertToAgentNotebookHistoryInfo(List logEntries, DataRow jobRow, JobStepCollection steps) + { + List jobs = new List(); + // get all the values for a job history + foreach (ILogEntry entry in logEntries) + { + // Make a new AgentJobHistoryInfo object + var jobHistoryInfo = new AgentNotebookHistoryInfo(); + jobHistoryInfo.InstanceId = Convert.ToInt32(jobRow[UrnInstanceID], System.Globalization.CultureInfo.InvariantCulture); + jobHistoryInfo.JobId = (Guid) jobRow[UrnJobId]; + var logEntry = entry as LogSourceJobHistory.LogEntryJobHistory; + jobHistoryInfo.RunStatus = entry.Severity == SeverityClass.Error ? 0 : 1; + jobHistoryInfo.SqlMessageId = logEntry.SqlMessageID; + jobHistoryInfo.Message = logEntry.Message; + jobHistoryInfo.StepId = logEntry.StepID; + jobHistoryInfo.StepName = logEntry.StepName; + jobHistoryInfo.SqlSeverity = logEntry.SqlSeverity; + jobHistoryInfo.JobName = logEntry.JobName; + jobHistoryInfo.RunDate = entry.PointInTime; + jobHistoryInfo.RunDuration = logEntry.Duration; + jobHistoryInfo.OperatorEmailed = logEntry.OperatorEmailed; + jobHistoryInfo.OperatorNetsent = logEntry.OperatorNetsent; + jobHistoryInfo.OperatorPaged = logEntry.OperatorPaged; + jobHistoryInfo.RetriesAttempted = logEntry.RetriesAttempted; + jobHistoryInfo.Server = logEntry.Server; + + // Add steps to the job if any + var jobSteps = new List(); + foreach (LogSourceJobHistory.LogEntryJobHistory subEntry in entry.SubEntries) + { + if (steps.Contains(subEntry.StepName)) + { + var jobId = jobRow[UrnJobId].ToString(); + jobSteps.Add(AgentUtilities.ConvertToAgentJobStep(steps.ItemById(Convert.ToInt32(subEntry.StepID)), logEntry, jobId)); + } + } + jobHistoryInfo.Steps = jobSteps.ToArray(); + jobs.Add(jobHistoryInfo); + } + return jobs; + } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobHistoryInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobHistoryInfo.cs index b6f39cb1..1cf230a8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobHistoryInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobHistoryInfo.cs @@ -58,4 +58,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent.Contracts public CompletionResult runStatus; public AgentJobStepInfo stepDetails; } + + /// + /// a class for storing various properties of a agent notebook history + /// + public class AgentNotebookHistoryInfo : AgentJobHistoryInfo + { + public int MaterializedNotebookId { get; set; } + public int MaterializedNotebookErrorFlag { get; set; } + public string MaterializedNotebookErrorInfo { get; set; } + } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobInfo.cs index 8ee7a9af..e6fe1449 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobInfo.cs @@ -61,4 +61,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent.Contracts public AgentScheduleInfo[] JobSchedules { get; set; } public AgentAlertInfo[] Alerts { get; set; } } + + /// + /// a class for storing variour properties of notebook Jobs + /// + public class AgentNotebookInfo : AgentJobInfo + { + public string TemplateId { get; set; } + public string TargetDatabase { get; set; } + public string LastRunNotebookError { get; set; } + public string ExecuteDatabase { get; set; } + } + } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentNotebookRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentNotebookRequest.cs new file mode 100644 index 00000000..1a7c2c95 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentNotebookRequest.cs @@ -0,0 +1,215 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.TaskServices; +using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.Agent.Contracts +{ + /// + /// SQL Agent Notebooks activity parameters + /// + public class AgentNotebooksParams : GeneralRequestDetails + { + public string OwnerUri { get; set; } + + } + + /// + /// SQL Agent Notebook activity result + /// + public class AgentNotebooksResult : ResultStatus + { + public AgentNotebookInfo[] Notebooks { get; set; } + } + + /// + /// SQL Agent Notebook request type + /// + public class AgentNotebooksRequest + { + /// + /// Request definition + /// + public static readonly + RequestType Type = + RequestType.Create("agent/notebooks"); + } + + /// + /// SQL Agent Notebook history parameters + /// + public class AgentNotebookHistoryParams : GeneralRequestDetails + { + public string OwnerUri { get; set; } + public string JobId { get; set; } + public string TargetDatabase { get; set; } + public string JobName { get; set; } + } + + /// + /// SQL Agent Notebook history results + /// + public class AgentNotebookHistoryResult : ResultStatus + { + public AgentNotebookHistoryInfo[] Histories { get; set; } + public AgentJobStepInfo[] Steps { get; set; } + + public AgentScheduleInfo[] Schedules { get; set; } + } + + /// + /// SQL Agent Notebook history request type + /// + public class AgentNotebookHistoryRequest + { + /// + /// Request definition + /// + public static readonly + RequestType Type = + RequestType.Create("agent/notebookhistory"); + } + + /// + /// SQL Agent create Notebook params + /// + public class CreateAgentNotebookParams : TaskRequestDetails + { + public string OwnerUri { get; set; } + public AgentNotebookInfo Notebook { get; set; } + public string TemplateFilePath { get; set; } + } + + /// + /// SQL Agent create Notebook result + /// + public class CreateAgentNotebookResult : ResultStatus + { + public AgentNotebookInfo Job { get; set; } + } + + /// + /// SQL Agent create Notebook request type + /// + public class CreateAgentNotebookRequest + { + public static readonly + RequestType Type = + RequestType.Create("agent/createnotebook"); + } + + /// + /// SQL Agent update Notebook params + /// + public class UpdateAgentNotebookParams : TaskRequestDetails + { + public string OwnerUri { get; set; } + public string OriginalNotebookName { get; set; } + public AgentNotebookInfo Notebook { get; set; } + public string TemplateFilePath { get; set; } + } + + /// + /// SQL Agent update Notebook result + /// + public class UpdateAgentNotebookResult : ResultStatus + { + } + + /// + /// SQL Agent update Notebook request type + /// + public class UpdateAgentNotebookRequest + { + /// + /// Request definition + /// + public static readonly + RequestType Type = + RequestType.Create("agent/updatenotebook"); + } + + /// + /// SQL Agent delete Notebook params + /// + public class DeleteAgentNotebookParams : TaskRequestDetails + { + public string OwnerUri { get; set; } + public AgentNotebookInfo Notebook { get; set; } + } + + /// + /// SQL Agent delete Notebook request type + /// + public class DeleteAgentNotebookRequest + { + /// + /// Request definition + /// + public static readonly + RequestType Type = + RequestType.Create("agent/deletenotebook"); + } + + /// + /// SQL Agent Notebook materialized params + /// + public class AgentNotebookMaterializedParams : TaskRequestDetails + { + public string OwnerUri { get; set; } + public string TargetDatabase { get; set; } + public int NotebookMaterializedId { get; set; } + } + + /// + /// SQL Agent Notebook materialized result + /// + public class AgentNotebookMaterializedResult : ResultStatus + { + public string NotebookMaterialized { get; set; } + } + + /// + /// SQL Agent Notebook materialized request type + /// + public class AgentNotebookMaterializedRequest + { + public static readonly + RequestType Type = + RequestType.Create("agent/notebookmaterialized"); + } + + /// + /// SQL Agent Notebook templates params + /// + public class AgentNotebookTemplateParams : TaskRequestDetails + { + public string OwnerUri { get; set; } + public string JobId { get; set; } + public string TargetDatabase { get; set; } + + } + + /// + /// SQL Agent Notebook templates results + /// + public class AgentNotebookTemplateResult : ResultStatus + { + public string NotebookTemplate { get; set; } + } + + /// + /// SQL Agent Notebook templates request type + /// + public class AgentNotebookTemplateRequest + { + public static readonly + RequestType Type = + RequestType.Create("agent/notebooktemplate"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentNotebookHelper.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentNotebookHelper.cs new file mode 100644 index 00000000..45fb65d4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/AgentNotebookHelper.cs @@ -0,0 +1,593 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Data.SqlClient; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Agent.Contracts; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlServer.Management.Smo.Agent; +using Microsoft.SqlTools.ServiceLayer.Management; + +namespace Microsoft.SqlTools.ServiceLayer.Agent +{ + internal class AgentNotebookHelper + { + /// + /// executes sql queries required by other agent notebook helper functions + /// + /// connectionInfo generated from OwnerUri + /// actual sql query to be executed + /// sql parameters required by the query + /// the database on which the query will be executed + /// + public static async Task ExecuteSqlQueries( + ConnectionInfo connInfo, + string sqlQuery, + List queryParameters = null, + string targetDatabase = null) + { + DataSet result = new DataSet(); + string originalConnectionDatabase = connInfo.ConnectionDetails.DatabaseName; + if (!string.IsNullOrWhiteSpace(targetDatabase)) + { + connInfo.ConnectionDetails.DatabaseName = targetDatabase; + } + using (SqlConnection connection = new SqlConnection(ConnectionService.BuildConnectionString(connInfo.ConnectionDetails))) + { + await connection.OpenAsync(); + using (SqlCommand sqlQueryCommand = new SqlCommand(sqlQuery, connection)) + { + if (queryParameters != null) + { + sqlQueryCommand.Parameters.AddRange(queryParameters.ToArray()); + } + SqlDataAdapter sqlCommandAdapter = new SqlDataAdapter(sqlQueryCommand); + sqlCommandAdapter.Fill(result); + } + } + connInfo.ConnectionDetails.DatabaseName = originalConnectionDatabase; + return result; + } + + /// + /// a function which fetches notebooks jobs accessible to the user + /// + /// connectionInfo generated from OwnerUri + /// array of agent notebooks + public static async Task GetAgentNotebooks(ConnectionInfo connInfo) + { + AgentNotebookInfo[] result; + // Fetching all agent Jobs accessible to the user + var serverConnection = ConnectionService.OpenServerConnection(connInfo); + var fetcher = new JobFetcher(serverConnection); + var filter = new JobActivityFilter(); + var jobs = fetcher.FetchJobs(filter); + + + Dictionary allJobsHashTable = new Dictionary(); + if (jobs != null) + { + foreach (var job in jobs.Values) + { + allJobsHashTable.Add(job.JobID, job); + } + } + // Fetching notebooks across all databases accessible by the user + string getJobIdsFromDatabaseQueryString = + @" + DECLARE @script AS VARCHAR(MAX) + SET @script = + ' + USE [?]; + IF EXISTS + ( + SELECT * FROM INFORMATION_SCHEMA.TABLES + WHERE + TABLE_SCHEMA = N''notebooks'' + AND + TABLE_NAME = N''nb_template'' + ) + BEGIN + SELECT + [notebooks].[nb_template].job_id, + [notebooks].[nb_template].template_id, + [notebooks].[nb_template].last_run_notebook_error, + [notebooks].[nb_template].execute_database, + DB_NAME() AS db_name + FROM [?].notebooks.nb_template + INNER JOIN + msdb.dbo.sysjobs + ON + [?].notebooks.nb_template.job_id = msdb.dbo.sysjobs.job_id + END + ' + EXEC sp_MSforeachdb @script"; + var agentNotebooks = new List(); + DataSet jobIdsDataSet = await ExecuteSqlQueries(connInfo, getJobIdsFromDatabaseQueryString); + foreach (DataTable templateTable in jobIdsDataSet.Tables) + { + foreach (DataRow templateRow in templateTable.Rows) + { + AgentNotebookInfo notebookJob = + AgentUtilities.ConvertToAgentNotebookInfo(allJobsHashTable[(Guid)templateRow["job_id"]]); + notebookJob.TemplateId = templateRow["template_id"] as string; + notebookJob.TargetDatabase = templateRow["db_name"] as string; + notebookJob.LastRunNotebookError = templateRow["last_run_notebook_error"] as string; + notebookJob.ExecuteDatabase = templateRow["execute_database"] as string; + agentNotebooks.Add(notebookJob); + } + } + result = agentNotebooks.ToArray(); + return result; + } + + + public static async Task CreateNotebook( + AgentService agentServiceInstance, + string ownerUri, + AgentNotebookInfo notebook, + string templatePath, + RunType runType) + { + if (!File.Exists(templatePath)) + { + throw new FileNotFoundException(); + } + AgentNotebookInfo result; + ConnectionInfo connInfo; + agentServiceInstance.ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); + + // creating notebook job step + notebook.JobSteps = CreateNotebookPowerShellStep(notebook.Name, notebook.TargetDatabase); + + // creating sql agent job + var jobCreationResult = await agentServiceInstance.ConfigureAgentJob( + ownerUri, + notebook.Name, + notebook, + ConfigAction.Create, + runType); + + if (jobCreationResult.Item1 == false) + { + throw new Exception(jobCreationResult.Item2); + } + + // creating notebook metadata for the job + string jobId = + await SetUpNotebookAndGetJobId( + connInfo, + notebook.Name, + notebook.TargetDatabase, + templatePath, + notebook.ExecuteDatabase); + notebook.JobId = jobId; + result = notebook; + return result; + } + + internal static async Task DeleteNotebook( + AgentService agentServiceInstance, + string ownerUri, + AgentNotebookInfo notebook, + RunType runType) + { + ConnectionInfo connInfo; + agentServiceInstance.ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); + + // deleting job from sql agent + var deleteJobResult = await agentServiceInstance.ConfigureAgentJob( + ownerUri, + notebook.Name, + notebook, + ConfigAction.Drop, + runType); + + if(!deleteJobResult.Item1) + { + throw new Exception(deleteJobResult.Item2); + } + // deleting notebook metadata from target database + await DeleteNotebookMetadata( + connInfo, + notebook.JobId, + notebook.TargetDatabase); + + + } + + internal static async Task UpdateNotebook( + AgentService agentServiceInstance, + string ownerUri, + string originalNotebookName, + AgentNotebookInfo notebook, + string templatePath, + RunType runType) + { + + ConnectionInfo connInfo; + agentServiceInstance.ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); + + if (!string.IsNullOrEmpty(templatePath) && !File.Exists(templatePath)) + { + throw new FileNotFoundException(); + } + + // updating notebook agent job + var updateJobResult = + await agentServiceInstance.ConfigureAgentJob( + ownerUri, + originalNotebookName, + notebook, + ConfigAction.Update, + runType); + + if(!updateJobResult.Item1) + { + throw new Exception(updateJobResult.Item2); + } + + // update notebook metadata + UpdateNotebookInfo( + connInfo, + notebook.JobId, + templatePath, + notebook.ExecuteDatabase, + notebook.TargetDatabase); + + } + + /// + /// fetches all notebook histories for a particular notebook job + /// + /// connectionInfo generated from OwnerUri + /// unique ID of the sql agent notebook job + /// database used to store notebook metadata + /// array of notebook history info + public static async Task GetAgentNotebookHistories( + ConnectionInfo connInfo, + string JobId, + string targetDatabase) + { + DataTable result; + string getNotebookHistoryQueryString = + @" + SELECT + materialized_id, + run_time, + run_date, + notebook_error + FROM + notebooks.nb_materialized + WHERE JOB_ID = @jobId"; + List getNotebookHistoryQueryParams = new List(); + getNotebookHistoryQueryParams.Add(new SqlParameter("jobId", JobId)); + DataSet notebookHistoriesDataSet = + await ExecuteSqlQueries( + connInfo, + getNotebookHistoryQueryString, + getNotebookHistoryQueryParams, + targetDatabase); + result = notebookHistoriesDataSet.Tables[0]; + return result; + } + + /// + /// + /// + /// + /// + /// + /// + public static async Task GetMaterializedNotebook( + ConnectionInfo connInfo, + int materializedId, + string targetDatabase) + { + string materializedNotebookQueryString = + @" + SELECT + notebook + FROM + notebooks.nb_materialized + WHERE + materialized_id = @notebookMaterializedID"; + List materializedNotebookQueryParams = new List(); + materializedNotebookQueryParams.Add(new SqlParameter("notebookMaterializedID", materializedId)); + DataSet materializedNotebookDataSet = + await ExecuteSqlQueries( + connInfo, + materializedNotebookQueryString, + materializedNotebookQueryParams, + targetDatabase); + DataTable materializedNotebookTable = materializedNotebookDataSet.Tables[0]; + DataRow materializedNotebookRows = materializedNotebookTable.Rows[0]; + return materializedNotebookRows["notebook"] as string; + } + + public static async Task GetTemplateNotebook( + ConnectionInfo connInfo, + string jobId, + string targetDatabase) + { + string templateNotebookQueryString = + @" + SELECT + notebook + FROM + notebooks.nb_template + WHERE + job_id = @jobId"; + List templateNotebookQueryParams = new List(); + templateNotebookQueryParams.Add(new SqlParameter("jobId", jobId)); + DataSet templateNotebookDataSet = + await ExecuteSqlQueries( + connInfo, + templateNotebookQueryString, + templateNotebookQueryParams, + targetDatabase); + DataTable templateNotebookTable = templateNotebookDataSet.Tables[0]; + DataRow templateNotebookRows = templateNotebookTable.Rows[0]; + return templateNotebookRows["notebook"] as string; + } + + /// + /// + /// + /// + /// + /// + public static AgentJobStepInfo[] CreateNotebookPowerShellStep( + string notebookName, + string storageDatabase) + { + AgentJobStepInfo[] result; + var assembly = Assembly.GetAssembly(typeof(AgentService)); + string execNotebookScript; + string notebookScriptResourcePath = "Microsoft.SqlTools.ServiceLayer.Agent.NotebookResources.NotebookJobScript.ps1"; + using (Stream scriptStream = assembly.GetManifestResourceStream(notebookScriptResourcePath)) + { + using (StreamReader reader = new StreamReader(scriptStream)) + { + execNotebookScript = + "$TargetDatabase = \"" + + storageDatabase + + "\"" + + Environment.NewLine + + reader.ReadToEnd(); + } + } + result = new AgentJobStepInfo[1]; + result[0] = new AgentJobStepInfo() + { + AppendLogToTable = false, + AppendToLogFile = false, + AppendToStepHist = false, + Command = execNotebookScript, + CommandExecutionSuccessCode = 0, + DatabaseName = "", + DatabaseUserName = null, + FailStepId = 0, + FailureAction = StepCompletionAction.QuitWithFailure, + Id = 1, + JobId = null, + JobName = notebookName, + OutputFileName = null, + ProxyName = null, + RetryAttempts = 0, + RetryInterval = 0, + Script = execNotebookScript, + ScriptName = null, + Server = "", + StepName = "Exec-Notebook", + SubSystem = AgentSubSystem.PowerShell, + SuccessAction = StepCompletionAction.QuitWithSuccess, + SuccessStepId = 0, + WriteLogToTable = false, + }; + return result; + } + + public static async Task SetUpNotebookAndGetJobId( + ConnectionInfo connInfo, + string notebookName, + string targetDatabase, + string templatePath, + string executionDatabase) + { + string jobId; + string notebookDatabaseSetupQueryString = + @" + IF NOT EXISTS ( + SELECT schema_name + FROM information_schema.schemata + WHERE schema_name = 'notebooks' ) + BEGIN + EXEC sp_executesql N'CREATE SCHEMA notebooks' + END + + IF NOT EXISTS (SELECT * FROM sys.objects + WHERE object_id = OBJECT_ID(N'[notebooks].[nb_template]') AND TYPE IN (N'U')) + BEGIN + CREATE TABLE [notebooks].[nb_template]( + template_id INT PRIMARY KEY IDENTITY(1,1), + job_id UNIQUEIDENTIFIER NOT NULL, + notebook NVARCHAR(MAX), + execute_database NVARCHAR(MAX), + last_run_notebook_error NVARCHAR(MAX) + ) + END + + IF NOT EXISTS (SELECT * FROM sys.objects + WHERE object_id = OBJECT_ID(N'[notebooks].[nb_materialized]') AND TYPE IN (N'U')) + BEGIN + CREATE TABLE [notebooks].[nb_materialized]( + materialized_id INT PRIMARY KEY IDENTITY(1,1), + job_id UNIQUEIDENTIFIER NOT NULL, + run_time VARCHAR(100), + run_date VARCHAR(100), + notebook NVARCHAR(MAX), + notebook_error NVARCHAR(MAX) + ) + END + USE [msdb]; + SELECT + job_id + FROM + msdb.dbo.sysjobs + WHERE + name= @jobName; + "; + List notebookDatabaseSetupQueryParams = new List(); + notebookDatabaseSetupQueryParams.Add(new SqlParameter("jobName", notebookName)); + DataSet jobIdDataSet = + await ExecuteSqlQueries( + connInfo, + notebookDatabaseSetupQueryString, + notebookDatabaseSetupQueryParams, + targetDatabase); + DataTable jobIdDataTable = jobIdDataSet.Tables[0]; + DataRow jobIdDataRow = jobIdDataTable.Rows[0]; + jobId = ((Guid)jobIdDataRow["job_id"]).ToString(); + StoreNotebookTemplate( + connInfo, + jobId, + templatePath, + targetDatabase, + executionDatabase); + return jobId; + } + + static async void StoreNotebookTemplate( + ConnectionInfo connInfo, + string jobId, + string templatePath, + string targetDatabase, + string executionDatabase) + { + string templateFileContents = File.ReadAllText(templatePath); + string insertTemplateJsonQuery = + @" + INSERT + INTO + notebooks.nb_template( + job_id, + notebook, + last_run_notebook_error, + execute_database) + VALUES + (@jobId, @templateFileContents, N'', @executeDatabase) + "; + List insertTemplateJsonQueryParams = new List(); + insertTemplateJsonQueryParams.Add(new SqlParameter("jobId", jobId)); + insertTemplateJsonQueryParams.Add(new SqlParameter("templateFileContents", templateFileContents)); + insertTemplateJsonQueryParams.Add(new SqlParameter("executeDatabase", executionDatabase)); + await ExecuteSqlQueries( + connInfo, + insertTemplateJsonQuery, + insertTemplateJsonQueryParams, + targetDatabase); + } + + public static async Task DeleteNotebookMetadata(ConnectionInfo connInfo, string jobId, string targetDatabase) + { + string deleteNotebookRowQuery = + @" + DELETE FROM notebooks.nb_template + WHERE + job_id = @jobId; + DELETE FROM notebooks.nb_materialized + WHERE + job_id = @jobId; + IF NOT EXISTS (SELECT * FROM notebooks.nb_template) + BEGIN + DROP TABLE notebooks.nb_template; + DROP TABLE notebooks.nb_materialized; + DROP SCHEMA notebooks; + END + "; + List deleteNotebookRowQueryParams = new List(); + deleteNotebookRowQueryParams.Add(new SqlParameter("jobId", jobId)); + await ExecuteSqlQueries(connInfo, deleteNotebookRowQuery, deleteNotebookRowQueryParams, targetDatabase); + } + + public static async void UpdateNotebookInfo( + ConnectionInfo connInfo, + string jobId, + string templatePath, + string executionDatabase, + string targetDatabase) + { + if (templatePath != null) + { + string templateFileContents = File.ReadAllText(templatePath); + string insertTemplateJsonQuery = + @" + UPDATE notebooks.nb_template + SET + notebook = @templateFileContents + WHERE + job_id = @jobId + "; + List insertTemplateJsonQueryParams = new List(); + insertTemplateJsonQueryParams.Add(new SqlParameter("templateFileContents", templateFileContents)); + insertTemplateJsonQueryParams.Add(new SqlParameter("jobId", jobId)); + await ExecuteSqlQueries( + connInfo, + insertTemplateJsonQuery, + insertTemplateJsonQueryParams, + targetDatabase); + } + string updateExecuteDatabaseQuery = + @" + UPDATE notebooks.nb_template + SET + execute_database = @executeDatabase + WHERE + job_id = @jobId + "; + List updateExecuteDatabaseQueryParams = new List(); + updateExecuteDatabaseQueryParams.Add(new SqlParameter("executeDatabase", executionDatabase)); + updateExecuteDatabaseQueryParams.Add(new SqlParameter("jobId", jobId)); + await ExecuteSqlQueries( + connInfo, + updateExecuteDatabaseQuery, + updateExecuteDatabaseQueryParams, + targetDatabase); + } + + public static async Task GetTemplateFile( + ConnectionInfo connInfo, + string job_id, + string targetDatabase, + string templateFileContents) + { + String getNotebookTemplateQuery = + @" + SELECT notebook + from + notebooks.nb_template + where + job_id = @jobId; + "; + List getNotebookTemplateQueryParams = new List(); + getNotebookTemplateQueryParams.Add(new SqlParameter("job_id", getNotebookTemplateQueryParams)); + DataSet templateDataSet = await AgentNotebookHelper.ExecuteSqlQueries( + connInfo, + getNotebookTemplateQuery, + getNotebookTemplateQueryParams, + targetDatabase); + + DataTable templateDataTable = templateDataSet.Tables[0]; + DataRow templateDataRow = templateDataTable.Rows[0]; + return templateDataRow["notebook"] as string; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/NotebookResources/NotebookJobScript.ps1 b/src/Microsoft.SqlTools.ServiceLayer/Agent/NotebookResources/NotebookJobScript.ps1 new file mode 100644 index 00000000..75dd3df7 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/NotebookResources/NotebookJobScript.ps1 @@ -0,0 +1,149 @@ +$JobId = "$(ESCAPE_SQUOTE(JOBID))" +$StartTime = "$(ESCAPE_SQUOTE(STRTTM))" +$StartDate = "$(ESCAPE_SQUOTE(STRTDT))" +$JSONTable = "select * from notebooks.nb_template where job_id = $JobId" +$sqlResult = Invoke-Sqlcmd -Query $JSONTable -Database $TargetDatabase -MaxCharLength 2147483647 +$FirstNotebookError = "" +function ParseTableToNotebookOutput { + param ( + [System.Data.DataTable] + $DataTable, + + [int] + $CellExecutionCount + ) + $TableHTMLText = "" + $TableSchemaFeilds = @() + $TableHTMLText += "" + foreach ($ColumnName in $DataTable.Columns) { + $TableSchemaFeilds += @(@{name = $ColumnName.toString() }) + $TableHTMLText += "" + } + $TableHTMLText += "" + $TableSchema = @{ } + $TableSchema["fields"] = $TableSchemaFeilds + + $TableDataRows = @() + foreach ($Row in $DataTable) { + $TableDataRow = [ordered]@{ } + $TableHTMLText += "" + $i = 0 + foreach ($Cell in $Row.ItemArray) { + $TableDataRow[$i.ToString()] = $Cell.toString() + $TableHTMLText += "" + $i++ + } + $TableHTMLText += "" + $TableDataRows += $TableDataRow + } + $TableDataResource = @{ } + $TableDataResource["schema"] = $TableSchema + $TableDataResource["data"] = $TableDataRows + $TableData = @{ } + $TableData["application/vnd.dataresource+json"] = $TableDataResource + $TableData["text/html"] = $TableHTMLText + $TableOutput = @{ } + $TableOutput["output_type"] = "execute_result" + $TableOutput["data"] = $TableData + $TableOutput["metadata"] = @{ } + $TableOutput["execution_count"] = $CellExecutionCount + return $TableOutput +} + +function ParseQueryErrorToNotebookOutput { + param ( + $QueryError + ) + $ErrorString = "Msg " + $QueryError.Exception.InnerException.Number + + ", Level " + $QueryError.Exception.InnerException.Class + + ", State " + $QueryError.Exception.InnerException.State + + ", Line " + $QueryError.Exception.InnerException.LineNumber + + "`r`n" + $QueryError.Exception.Message + + $ErrorOutput = @{ } + $ErrorOutput["output_type"] = "error" + $ErrorOutput["traceback"] = @() + $ErrorOutput["evalue"] = $ErrorString + return $ErrorOutput +} + +function ParseStringToNotebookOutput { + param ( + [System.String] + $InputString + ) + $StringOutputData = @{ } + $StringOutputData["text/html"] = $InputString + $StringOutput = @{ } + $StringOutput["output_type"] = "display_data" + $StringOutput["data"] = $StringOutputData + $StringOutput["metadata"] = @{ } + return $StringOutput +} + +$TemplateNotebook = $sqlResult.notebook +$executeDatabase = $sqlResult.execute_database +try { + $TemplateNotebookJsonObject = ConvertFrom-Json -InputObject $TemplateNotebook +} +catch { + Throw $_.Exception +} + +$DatabaseQueryHashTable = @{ } +$DatabaseQueryHashTable["Verbose"] = $true +$DatabaseQueryHashTable["ErrorVariable"] = "SqlQueryError" +$DatabaseQueryHashTable["OutputAs"] = "DataTables" +$DatabaseQueryHashTable["Database"] = $executeDatabase +$CellExcecutionCount = 1 + +foreach ($NotebookCell in $TemplateNotebookJsonObject.cells) { + $NotebookCellOutputs = @() + if ($NotebookCell.cell_type -eq "markdown" -or $NotebookCell.cell_type -eq "raw" -or $NotebookCell.source -eq "") { + continue; + } + switch($NotebookCell.source.getType()){ + System.Object[] { + $DatabaseQueryHashTable["Query"] = ($NotebookCell.source -join "`r`n" | Out-String) + } + String { + $DatabaseQueryHashTable["Query"] = $NotebookCell.source + } + } + $SqlQueryExecutionTime = Measure-Command { $SqlQueryResult = @(Invoke-Sqlcmd @DatabaseQueryHashTable 4>&1) } + $NotebookCell.execution_count = $CellExcecutionCount++ + $NotebookCellTableOutputs = @() + if ($SqlQueryResult) { + foreach ($SQLQueryResultElement in $SqlQueryResult) { + switch ($SQLQueryResultElement.getType()) { + System.Management.Automation.VerboseRecord { + $NotebookCellOutputs += ParseStringToNotebookOutput($SQLQueryResultElement.Message) + } + System.Data.DataTable { + $NotebookCellTableOutputs += ParseTableToNotebookOutput $SQLQueryResultElement $CellExcecutionCount + } + Default { } + } + } + } + if ($SqlQueryError) { + if(!$FirstNotebookError){ + $FirstNotebookError = $SqlQueryError.Exception.Message.Replace("'", "''") + } + $NotebookCellOutputs += ParseQueryErrorToNotebookOutput($SqlQueryError) + } + if ($SqlQueryExecutionTime) { + $NotebookCellExcutionTimeString = "Total execution time: " + $SqlQueryExecutionTime.ToString("hh\:mm\:ss\.fff") + $NotebookCellOutputs += ParseStringToNotebookOutput($NotebookCellExcutionTimeString) + } + $NotebookCellOutputs += $NotebookCellTableOutputs + $NotebookCell.outputs = $NotebookCellOutputs +} + +$result = ($TemplateNotebookJsonObject | ConvertTo-Json -Depth 100) +Write-Output $result +$result = $result.Replace("'","''") +$InsertQuery = "INSERT INTO notebooks.nb_materialized (job_id, run_time, run_date, notebook, notebook_error) VALUES ($JobID, '$StartTime', '$StartDate','$result','$FirstNotebookError')" +$SqlResult = Invoke-Sqlcmd -Query $InsertQuery -Database $TargetDatabase +$InsertQuery = "UPDATE notebooks.nb_template SET last_run_notebook_error = '$FirstNotebookError' where job_id = $JobID" +$SqlResult = Invoke-Sqlcmd -Query $InsertQuery -Database $TargetDatabase \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj index cad3b3d7..49b54394 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj +++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj @@ -39,4 +39,7 @@ + + + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj] b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj] new file mode 100644 index 00000000..e69de29b diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/FileUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/FileUtils.cs index d82dfa5e..1a54128a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/FileUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/FileUtils.cs @@ -9,6 +9,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility internal static class FileUtilities { internal static string PeekDefinitionTempFolder = Path.GetTempPath() + "mssql_definition"; + internal static string AgentNotebookTempFolder = Path.GetTempPath() + "mssql_notebooks"; internal static bool PeekDefinitionTempFolderCreated = false; internal static string GetPeekDefinitionTempFolder() diff --git a/test/CodeCoverage/gulpfile.js b/test/CodeCoverage/gulpfile.js index 66620bdd..f274da01 100644 --- a/test/CodeCoverage/gulpfile.js +++ b/test/CodeCoverage/gulpfile.js @@ -92,7 +92,7 @@ gulp.task('ext:nuget-restore', function() { gulp.task('ext:code-coverage', function(done) { - cproc.execFile('cmd.exe', [ '/c', 'codecoverage.bat' ], {maxBuffer: 1024 * 500}, function(err, stdout) { + cproc.execFile('cmd.exe', [ '/c', 'codecoverage.bat' ], {maxBuffer: 1024 * 1000}, function(err, stdout) { if (err) { throw new gutil.PluginError('ext:code-coverage', err); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentNotebookTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentNotebookTests.cs new file mode 100644 index 00000000..28f0d717 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentNotebookTests.cs @@ -0,0 +1,288 @@ +using System; +using System.Threading.Tasks; +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Agent; +using Microsoft.SqlTools.ServiceLayer.Agent.Contracts; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using Microsoft.SqlTools.ServiceLayer.Utility; +using Microsoft.SqlTools.ServiceLayer.Management; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Moq; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent +{ + public class AgentNotebookTests + { + /// + /// Test case for fetch notebook jobs Request Handler + /// + [Fact] + public async Task TestHandleAgentNotebooksRequest() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + var fetchNotebooksContext = new Mock>(); + + fetchNotebooksContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + await service.HandleAgentNotebooksRequest(new AgentNotebooksParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri + }, fetchNotebooksContext.Object); + + fetchNotebooksContext.Verify(x => x.SendResult(It.Is(p => p.Success == true))); + + } + } + + /// + /// Tests the create job helper function + /// + [Fact] + internal async Task TestAgentNotebookCreateHelper() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + AgentNotebookInfo notebook = AgentTestUtils.GetTestNotebookInfo("myTestNotebookJob" + Guid.NewGuid().ToString(), "master"); + Assert.Equal(false, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + notebook = AgentTestUtils.SetupNotebookJob(connectionResult).Result; + Assert.Equal(true, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + await AgentTestUtils.CleanupNotebookJob(connectionResult, notebook); + } + } + + /// + /// Tests the create job request handler with an invalid file path + /// + [Fact] + internal async Task TestHandleCreateAgentNotebookRequestWithInvalidTemplatePath() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + AgentNotebookInfo notebook = AgentTestUtils.GetTestNotebookInfo("myTestNotebookJob" + Guid.NewGuid().ToString(), "master"); + var createNotebookContext = new Mock>(); + createNotebookContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + await service.HandleCreateAgentNotebookRequest(new CreateAgentNotebookParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + Notebook = notebook, + TemplateFilePath = "garbargepath" + }, createNotebookContext.Object); + + createNotebookContext.Verify(x => x.SendResult(It.Is(p => p.Success == false))); + Assert.Equal(false, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + } + } + + /// + /// creating a job with duplicate name + /// + [Fact] + internal async Task TestDuplicateJobCreation() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + AgentNotebookInfo notebook = AgentTestUtils.GetTestNotebookInfo("myTestNotebookJob" + Guid.NewGuid().ToString(), "master"); + var createNotebookContext = new Mock>(); + createNotebookContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + await service.HandleCreateAgentNotebookRequest(new CreateAgentNotebookParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + Notebook = notebook, + TemplateFilePath = AgentTestUtils.CreateTemplateNotebookFile() + }, createNotebookContext.Object); + + createNotebookContext.Verify(x => x.SendResult(It.Is(p => p.Success == true))); + await service.HandleCreateAgentNotebookRequest(new CreateAgentNotebookParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + Notebook = notebook, + TemplateFilePath = AgentTestUtils.CreateTemplateNotebookFile() + }, createNotebookContext.Object); + createNotebookContext.Verify(x => x.SendResult(It.Is(p => p.Success == false))); + await AgentTestUtils.CleanupNotebookJob(connectionResult, notebook); + } + } + + /// + /// Tests the create notebook job handler + /// + [Fact] + internal async Task TestCreateAgentNotebookHandler() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + AgentNotebookInfo notebook = AgentTestUtils.GetTestNotebookInfo("myTestNotebookJob" + Guid.NewGuid().ToString(), "master"); + var createNotebookContext = new Mock>(); + createNotebookContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + await service.HandleCreateAgentNotebookRequest(new CreateAgentNotebookParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + Notebook = notebook, + TemplateFilePath = AgentTestUtils.CreateTemplateNotebookFile() + }, createNotebookContext.Object); + createNotebookContext.Verify(x => x.SendResult(It.Is(p => p.Success == true))); + Assert.Equal(true, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + var createdNotebook = AgentTestUtils.GetNotebook(connectionResult, notebook.Name); + await AgentTestUtils.CleanupNotebookJob(connectionResult, createdNotebook); + } + } + + /// + /// Tests the delete notebook job handler + /// + [Fact] + internal async Task TestDeleteAgentNotebookHandler() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + //creating a notebook job + AgentNotebookInfo notebook = AgentTestUtils.SetupNotebookJob(connectionResult).Result; + //verifying it's getting created + Assert.Equal(true, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + //deleting the notebook job + var deleteNotebookContext = new Mock>(); + deleteNotebookContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + await service.HandleDeleteAgentNotebooksRequest(new DeleteAgentNotebookParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + Notebook = notebook + }, deleteNotebookContext.Object); + deleteNotebookContext.Verify(x => x.SendResult(It.Is(p => p.Success == true))); + //verifying if the job is deleted + Assert.Equal(false, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + } + } + + /// + /// deleting a existing notebook job + /// + [Fact] + internal async Task TestDeleteNonExistentJob() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + //getting a test notebook object + var notebook = AgentTestUtils.GetTestNotebookInfo("myTestNotebookJob" + Guid.NewGuid().ToString(), "master"); + + var deleteNotebookContext = new Mock>(); + deleteNotebookContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + await service.HandleDeleteAgentNotebooksRequest(new DeleteAgentNotebookParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + Notebook = notebook + }, deleteNotebookContext.Object); + //endpoint should error out + deleteNotebookContext.Verify(x => x.SendResult(It.Is(p => p.Success == false))); + } + } + + /// + /// updating a non existing notebook job + /// + [Fact] + internal async Task TestUpdateNonExistentJob() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + //getting a test notebook object + AgentNotebookInfo notebook = AgentTestUtils.GetTestNotebookInfo("myTestNotebookJob" + Guid.NewGuid().ToString(), "master"); + + var updateNotebookContext = new Mock>(); + updateNotebookContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + await service.HandleUpdateAgentNotebookRequest(new UpdateAgentNotebookParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + Notebook = notebook, + TemplateFilePath = AgentTestUtils.CreateTemplateNotebookFile() + }, updateNotebookContext.Object); + // enpoint should error out + updateNotebookContext.Verify(x => x.SendResult(It.Is(p => p.Success == false))); + } + } + + /// + /// update notebook handler with garbage path + /// + [Fact] + internal async Task TestUpdateWithGarbagePath() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + + //seting up a temp notebook job + var notebook = AgentTestUtils.SetupNotebookJob(connectionResult).Result; + //verifying that the notebook is created + Assert.Equal(true, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + + var updateNotebookContext = new Mock>(); + updateNotebookContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + //calling the endpoint with a garbage path + await service.HandleUpdateAgentNotebookRequest(new UpdateAgentNotebookParams() + { + OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + Notebook = notebook, + TemplateFilePath = "garbargepath" + }, updateNotebookContext.Object); + //the enpoint should return false + updateNotebookContext.Verify(x => x.SendResult(It.Is(p => p.Success == false))); + + //cleaning up the job + await AgentTestUtils.CleanupNotebookJob(connectionResult, notebook); + Assert.Equal(false, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + } + } + + [Fact] + internal async Task TestDeletingUpdatedJob() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var service = new AgentService(); + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + + //seting up a temp notebook job + var notebook = AgentTestUtils.SetupNotebookJob(connectionResult).Result; + //verifying that the notebook is created + Assert.Equal(true, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + + var originalName = notebook.Name; + //Changing the notebookName + notebook.Name = "myTestNotebookJob" + Guid.NewGuid().ToString(); + + Assert.Equal(false, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + + await AgentNotebookHelper.UpdateNotebook( + service, + connectionResult.ConnectionInfo.OwnerUri, + originalName, + notebook, + null, + ManagementUtils.asRunType(0) + ); + + Assert.Equal(true, AgentTestUtils.VerifyNotebook(connectionResult, notebook)); + + //cleaning up the job + await AgentTestUtils.CleanupNotebookJob(connectionResult, notebook); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs index c288cadc..5cb2893c 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs @@ -4,12 +4,14 @@ // using System; +using System.IO; +using System.Reflection; using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Agent; using Microsoft.SqlTools.ServiceLayer.Agent.Contracts; -using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security; +using Microsoft.SqlTools.ServiceLayer.Management; using Microsoft.SqlTools.ServiceLayer.Utility; using Moq; using static Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility.LiveConnectionHelper; @@ -22,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent internal static AgentJobStepInfo GetTestJobStepInfo( TestConnectionResult connectionResult, - AgentJobInfo job, + AgentJobInfo job, string stepName = "Test Job Step1") { return new AgentJobStepInfo() @@ -57,7 +59,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent CategoryType = 1, LastRun = "today", NextRun = "tomorrow", - JobId = Guid.NewGuid().ToString() + JobId = Guid.NewGuid().ToString(), + Owner = "sa" }; } @@ -74,12 +77,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent internal static AgentProxyInfo GetTestProxyInfo() { return new AgentProxyInfo() - { + { AccountName = "Test Proxy", CredentialName = SecurityTestUtils.TestCredentialName, Description = "Test proxy description", - IsEnabled = true - }; + IsEnabled = true + }; } internal static AgentScheduleInfo GetTestScheduleInfo() @@ -93,11 +96,11 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task CreateAgentJob( - AgentService service, - TestConnectionResult connectionResult, + AgentService service, + TestConnectionResult connectionResult, AgentJobInfo job) { - var context = new Mock>(); + var context = new Mock>(); await service.HandleCreateAgentJobRequest(new CreateAgentJobParams { OwnerUri = connectionResult.ConnectionInfo.OwnerUri, @@ -107,12 +110,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task UpdateAgentJob( - AgentService service, - TestConnectionResult connectionResult, + AgentService service, + TestConnectionResult connectionResult, AgentJobInfo job) { job.Description = "Update job description"; - var context = new Mock>(); + var context = new Mock>(); await service.HandleUpdateAgentJobRequest(new UpdateAgentJobParams { OwnerUri = connectionResult.ConnectionInfo.OwnerUri, @@ -122,12 +125,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task DeleteAgentJob( - AgentService service, - TestConnectionResult connectionResult, - AgentJobInfo job, + AgentService service, + TestConnectionResult connectionResult, + AgentJobInfo job, bool verify = true) { - var context = new Mock>(); + var context = new Mock>(); await service.HandleDeleteAgentJobRequest(new DeleteAgentJobParams { OwnerUri = connectionResult.ConnectionInfo.OwnerUri, @@ -141,11 +144,11 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task CreateAgentJobStep( - AgentService service, - TestConnectionResult connectionResult, + AgentService service, + TestConnectionResult connectionResult, AgentJobStepInfo stepInfo) { - var context = new Mock>(); + var context = new Mock>(); await service.HandleCreateAgentJobStepRequest(new CreateAgentJobStepParams { OwnerUri = connectionResult.ConnectionInfo.OwnerUri, @@ -153,13 +156,13 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent }, context.Object); context.VerifyAll(); } - + internal static async Task UpdateAgentJobStep( - AgentService service, - TestConnectionResult connectionResult, + AgentService service, + TestConnectionResult connectionResult, AgentJobStepInfo stepInfo) { - var context = new Mock>(); + var context = new Mock>(); await service.HandleUpdateAgentJobStepRequest(new UpdateAgentJobStepParams { OwnerUri = connectionResult.ConnectionInfo.OwnerUri, @@ -169,11 +172,11 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task DeleteAgentJobStep( - AgentService service, - TestConnectionResult connectionResult, + AgentService service, + TestConnectionResult connectionResult, AgentJobStepInfo stepInfo) { - var context = new Mock>(); + var context = new Mock>(); await service.HandleDeleteAgentJobStepRequest(new DeleteAgentJobStepParams { OwnerUri = connectionResult.ConnectionInfo.OwnerUri, @@ -183,7 +186,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task CreateAgentOperator( - AgentService service, + AgentService service, TestConnectionResult connectionResult, AgentOperatorInfo operatorInfo) { @@ -197,7 +200,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task UpdateAgentOperator( - AgentService service, + AgentService service, TestConnectionResult connectionResult, AgentOperatorInfo operatorInfo) { @@ -211,7 +214,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task DeleteAgentOperator( - AgentService service, + AgentService service, TestConnectionResult connectionResult, AgentOperatorInfo operatorInfo) { @@ -225,7 +228,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task CreateAgentProxy( - AgentService service, + AgentService service, TestConnectionResult connectionResult, AgentProxyInfo proxy) { @@ -239,7 +242,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task UpdateAgentProxy( - AgentService service, + AgentService service, TestConnectionResult connectionResult, string originalProxyName, AgentProxyInfo proxy) @@ -255,8 +258,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task DeleteAgentProxy( - AgentService service, - TestConnectionResult connectionResult, + AgentService service, + TestConnectionResult connectionResult, AgentProxyInfo proxy) { var context = new Mock>(); @@ -269,7 +272,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task CreateAgentSchedule( - AgentService service, + AgentService service, TestConnectionResult connectionResult, AgentScheduleInfo schedule) { @@ -277,16 +280,16 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent await service.HandleCreateAgentScheduleRequest(new CreateAgentScheduleParams { OwnerUri = connectionResult.ConnectionInfo.OwnerUri, - Schedule = schedule + Schedule = schedule }, context.Object); context.VerifyAll(); } - internal static async Task UpdateAgentSchedule( - AgentService service, - TestConnectionResult connectionResult, - string originalScheduleName, - AgentScheduleInfo schedule) + internal static async Task UpdateAgentSchedule( + AgentService service, + TestConnectionResult connectionResult, + string originalScheduleName, + AgentScheduleInfo schedule) { var context = new Mock>(); await service.HandleUpdateAgentScheduleRequest(new UpdateAgentScheduleParams() @@ -299,8 +302,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent } internal static async Task DeleteAgentSchedule( - AgentService service, - TestConnectionResult connectionResult, + AgentService service, + TestConnectionResult connectionResult, AgentScheduleInfo schedule) { var context = new Mock>(); @@ -346,5 +349,116 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent var service = new AgentService(); await DeleteAgentJob(service, connectionResult, job); } + + + + public static async Task SetupNotebookJob( + TestConnectionResult connectionResult, + AgentNotebookInfo notebook = null) + { + var service = new AgentService(); + if (notebook == null) + { + notebook = GetTestNotebookInfo("myTestNotebookJob" + Guid.NewGuid().ToString(), "master"); + } + string tempNotebookPath = CreateTemplateNotebookFile(); + + await AgentNotebookHelper.CreateNotebook( + service, + connectionResult.ConnectionInfo.OwnerUri, + notebook, + tempNotebookPath, + ManagementUtils.asRunType(0) + ); + + var createdNotebook = GetNotebook(connectionResult, notebook.Name); + File.Delete(tempNotebookPath); + return createdNotebook; + } + + public static async Task CleanupNotebookJob(TestConnectionResult connectionResult, AgentNotebookInfo notebook) + { + var service = new AgentService(); + await AgentNotebookHelper.DeleteNotebook( + service, + connectionResult.ConnectionInfo.OwnerUri, + notebook, + ManagementUtils.asRunType(0) + ); + } + + public static AgentNotebookInfo GetNotebook(TestConnectionResult connectionResult, string name){ + var notebookList = AgentNotebookHelper.GetAgentNotebooks(connectionResult.ConnectionInfo).Result; + foreach(AgentNotebookInfo n in notebookList) + { + if(n.Name == name) + { + return n; + } + } + return null; + } + + + public static bool VerifyNotebook(TestConnectionResult connectionResult, AgentNotebookInfo notebook) + { + var notebookList = AgentNotebookHelper.GetAgentNotebooks(connectionResult.ConnectionInfo).Result; + foreach (AgentNotebookInfo n in notebookList) + { + if (NotebookObjectEquals(notebook, n)) + { + return true; + } + } + return false; + } + static bool NotebookObjectEquals(AgentNotebookInfo expectedNotebook, AgentNotebookInfo actualNotebook) + { + return ( + expectedNotebook.Name == actualNotebook.Name + && + expectedNotebook.Description == actualNotebook.Description + ); + } + + internal static string CreateTemplateNotebookFile() + { + Assembly assembly = Assembly.GetAssembly(typeof(AgentNotebookTests)); + Stream scriptStream = assembly.GetManifestResourceStream(assembly.GetName().Name + ".Agent.NotebookResources.TestNotebook.ipynb"); + StreamReader reader = new StreamReader(scriptStream); + string testNotebookString = reader.ReadToEnd(); + string tempNotebookPath = System.IO.Path.GetTempFileName().Replace(".tmp", ".ipynb"); + File.WriteAllText(tempNotebookPath, testNotebookString); + return tempNotebookPath; + } + + internal static AgentNotebookInfo GetTestNotebookInfo(string TestJobName, string TargetDatabase) + { + return new AgentNotebookInfo() + { + Name = TestJobName, + Description = "Test job description", + CurrentExecutionStatus = JobExecutionStatus.Executing, + LastRunOutcome = CompletionResult.InProgress, + CurrentExecutionStep = "Step 1", + Enabled = false, + HasTarget = false, + HasSchedule = false, + HasStep = false, + Runnable = true, + Category = "Cateory 1", + CategoryId = 1, + CategoryType = 1, + LastRun = "today", + NextRun = "tomorrow", + JobId = new Guid().ToString(), + TargetDatabase = TargetDatabase, + Owner = "sa", + ExecuteDatabase = TargetDatabase, + JobSchedules = new AgentScheduleInfo[0], + Alerts = new AgentAlertInfo[0] + }; + } + } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/NotebookResources/testNotebook.ipynb b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/NotebookResources/testNotebook.ipynb new file mode 100644 index 00000000..d80028f0 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/NotebookResources/testNotebook.ipynb @@ -0,0 +1,24 @@ +{ + "metadata": { + "kernelspec": { + "name": "SQL", + "display_name": "SQL", + "language": "sql" + }, + "language_info": { + "name": "sql", + "version": "" + } + }, + "nbformat_minor": 2, + "nbformat": 4, + "cells": [ + { + "cell_type": "code", + "source": "select * from sys.all_objects", + "metadata": {}, + "outputs": [], + "execution_count": 2 + } + ] +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj index 6a18af3f..1ba0f988 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj @@ -44,4 +44,10 @@ $(DefineConstants);WINDOWS_ONLY_BUILD + + + + + +
" + $ColumnName.toString() + "
" + $Cell.toString() + "