/// <summary> /// Get the checkout table entries. /// </summary> /// <returns>The checkout table entries with the file id as a key.</returns> public IDictionary <string, SourceFile> GetCheckouts() { Dictionary <string, SourceFile> result = new Dictionary <string, SourceFile>(); if (!IsEnabled()) { return(result); } DataSelectResult lockTable = _client.Data.SelectAll(String.Format("SELECT {0}, {1}, {2}, {3}, {4} FROM {5}", ColumnEntityId, ColumnEntityName, ColumnEntityTypeName, ColumnUserId, ColumnUserName, TableName)); foreach (DataRow row in lockTable.Data.Rows) { SourceFile file = new SourceFile( row[ColumnEntityId] as string, row[ColumnEntityTypeName] as string, row[ColumnEntityName] as string, new User( row[ColumnUserId] as string, row[ColumnUserName] as string)); result.Add(file.Id, file); } return(result); }
/// <summary> /// Update the checkout status of the given file. /// </summary> /// <param name="file">The file to update the checkout status for.</param> public void RefreshCheckoutStatus(SourceFile file) { if (file == null) { throw new ArgumentNullException("file"); } if (String.IsNullOrEmpty(file.Id)) { throw new ArgumentException("The file doesn't have an id.", "file.Id"); } if (!IsEnabled()) { return; } DataSelectResult lockTable = _client.Data.SelectAll(String.Format("SELECT {0}, {1}, {2} FROM {3} WHERE {0} = '{4}'", ColumnEntityId, ColumnUserId, ColumnUserName, TableName, file.Id)); if (lockTable.Data.Rows.Count == 1) { file.CheckedOutBy = new User( lockTable.Data.Rows[0][ColumnUserId] as string, lockTable.Data.Rows[0][ColumnUserName] as string); } else { file.CheckedOutBy = null; } }
/// <summary> /// Get checkpoints that have been created. /// </summary> /// <returns>Existing checkpoints.</returns> public Checkpoint[] GetCheckpoints() { SalesForceAPI.Tooling.queryResponse response = _client.ToolingClient.query(new SalesForceAPI.Tooling.queryRequest( new SalesForceAPI.Tooling.SessionHeader() { sessionId = _client.SessionId }, String.Format("SELECT Id, ActionScript, ActionScriptType, ExecutableEntityId, ExpirationDate, IsDumpingHeap, Iteration, Line, ScopeId FROM ApexExecutionOverlayAction WHERE ScopeId = '{0}'", _client.User.Id))); List <Checkpoint> result = new List <Checkpoint>(); if (response.result.records != null) { // get file names Dictionary <string, string> idToFileNameMap = new Dictionary <string, string>(); foreach (SalesForceAPI.Tooling.sObject record in response.result.records) { if (!idToFileNameMap.ContainsKey((record as SalesForceAPI.Tooling.ApexExecutionOverlayAction).ExecutableEntityId)) { idToFileNameMap.Add( (record as SalesForceAPI.Tooling.ApexExecutionOverlayAction).ExecutableEntityId, String.Empty); } } DataSelectResult names = _client.Data.Select(String.Format( "SELECT Id, Name FROM ApexClass WHERE Id IN ('{0}')", String.Join("','", idToFileNameMap.Keys))); foreach (DataRow row in names.Data.Rows) { idToFileNameMap.Remove(row["Id"] as string); idToFileNameMap.Add(row["Id"] as string, row["Name"] as string); } names = _client.Data.Select(String.Format( "SELECT Id, Name FROM ApexTrigger WHERE Id IN ('{0}')", String.Join("','", idToFileNameMap.Keys))); foreach (DataRow row in names.Data.Rows) { idToFileNameMap.Remove(row["Id"] as string); idToFileNameMap.Add(row["Id"] as string, row["Name"] as string); } // create checkpoints foreach (SalesForceAPI.Tooling.sObject record in response.result.records) { result.Add(new Checkpoint( record as SalesForceAPI.Tooling.ApexExecutionOverlayAction, idToFileNameMap[(record as SalesForceAPI.Tooling.ApexExecutionOverlayAction).ExecutableEntityId])); } } return(result.ToArray()); }
/// <summary> /// Start tests for a given class. /// </summary> /// <param name="names">The names of the classes to start tests for.</param> /// <returns>The started test run.</returns> public TestRun StartTests(IEnumerable <string> names) { if (names == null) { throw new ArgumentNullException("names"); } // get class ids StringBuilder fileNameBuilder = new StringBuilder(); foreach (string name in names) { fileNameBuilder.AppendFormat("'{0}',", name); } fileNameBuilder.Length--; DataSelectResult classIdData = _client.Data.SelectAll( String.Format("SELECT Id, Name FROM ApexClass WHERE Name IN ({0})", fileNameBuilder.ToString())); // submit test run items Dictionary <string, string> apexClassNameMap = new Dictionary <string, string>(); DataTable dt = new DataTable("ApexTestQueueItem"); dt.Columns.Add("ApexClassId"); foreach (DataRow row in classIdData.Data.Rows) { DataRow testRunRow = dt.NewRow(); testRunRow["ApexClassId"] = row["Id"]; dt.Rows.Add(testRunRow); apexClassNameMap.Add(row["Id"] as string, row["Name"] as string); } _client.Data.Insert(dt); // create the test run object List <TestRunItem> items = new List <TestRunItem>(); foreach (DataRow row in dt.Rows) { items.Add(new TestRunItem( apexClassNameMap[row["ApexClassId"] as string], row["ApexClassId"] as string)); } DataSelectResult jobIdData = _client.Data.Select( String.Format("SELECT ParentJobId FROM ApexTestQueueItem WHERE Id = '{0}'", dt.Rows[0]["Id"])); return(new TestRun(jobIdData.Data.Rows[0]["ParentJobId"] as string, items.ToArray())); }
/// <summary> /// Create a new checkpoint. /// </summary> /// <param name="file">The file to create a checkpoint in.</param> /// <param name="lineNumber">The line number in the file to create the checkpoint for.</param> /// <param name="iteration">The number of iterations before the checkpoint is processed.</param> /// <param name="isHeapDump">If true a heap dump will be collected with the checkpoint.</param> /// <param name="script">An optional script to run with the checkpoint.</param> /// <param name="scriptType">The type of script specified.</param> /// <returns>The newly created checkpoint.</returns> public Checkpoint CreateCheckpoint( SourceFile file, int lineNumber, int iteration, bool isHeapDump, string script, CheckpointScriptType scriptType) { if (file == null) { throw new ArgumentNullException("file"); } // get record id string entityId = null; switch (file.FileType.Name) { case "ApexClass": case "ApexTrigger": DataSelectResult objectQueryResult = _client.Data.Select(String.Format("SELECT id FROM {0} WHERE Name = '{1}'", file.FileType.Name, file.Name)); if (objectQueryResult.Data.Rows.Count > 0) { entityId = objectQueryResult.Data.Rows[0]["id"] as string; } break; default: throw new Exception("Unsupported type: " + file.FileType.Name); } if (entityId == null) { throw new Exception("Couldn't get id for: " + file.Name); } return(CreateCheckpoint( entityId, file.Name, lineNumber, iteration, isHeapDump, script, scriptType)); }
/// <summary> /// Execute a select query on the server. This method returns all records instead of /// chunks of 200 records. /// </summary> /// <param name="query">The query to execute.</param> /// <param name="includeStructure">If true the table structure is set. i.e. readonly columns are set.</param> /// <returns>The result of the query.</returns> public DataSelectResult SelectAll(string query, bool includeStructure) { DataSelectResult result = Select(query, includeStructure); DataTable data = result.Data; while (result.IsMore) { result = Select(result); foreach (DataRow row in result.Data.Rows) { data.ImportRow(row); } } data.AcceptChanges(); return(new DataSelectResult(data, false, null, data.Rows.Count, 0)); }
/// <summary> /// Execute a select query on the server to get the next batch of data results. /// </summary> /// <param name="previousResult">The last batch of data results that were received.</param> /// <param name="includeStructure">If true the table structure is set. i.e. readonly columns are set.</param> /// <returns>The next batch of data results that follow the given previous data results.</returns> public DataSelectResult Select(DataSelectResult previousResult, bool includeStructure) { SalesForceAPI.Partner.queryMoreResponse response = _client.PartnerClient.queryMore(new SalesForceAPI.Partner.queryMoreRequest( new SalesForceAPI.Partner.SessionHeader() { sessionId = _client.SessionId }, null, null, previousResult.QueryLocator)); return(new DataSelectResult( Convert(response.result.records, includeStructure), !response.result.done, response.result.queryLocator, previousResult.Size, previousResult.Index + previousResult.Data.Rows.Count)); }
/// <summary> /// Update an existing checkout on a file. /// </summary> /// <param name="file">The file to update the checkout on.</param> public void UpdateCheckout(SourceFile file) { if (file == null) { throw new ArgumentNullException("file"); } if (String.IsNullOrEmpty(file.Id)) { throw new ArgumentException("The file doesn't have an id.", "file.Id"); } if (!IsEnabled()) { return; } DataSelectResult lockTable = _client.Data.SelectAll(String.Format("SELECT Id, {0}, {1}, {2}, {3}, {4} FROM {5} WHERE {0} = '{6}' AND {3} = '{7}'", ColumnEntityId, ColumnEntityName, ColumnEntityTypeName, ColumnUserId, ColumnUserName, TableName, file.Id, _client.User.Id)); if (lockTable.Data.Rows.Count != 1) { return; // file isn't checked out to the current user } lockTable.Data.Rows[0][ColumnEntityTypeName] = file.FileType.Name; lockTable.Data.Rows[0][ColumnEntityName] = file.Name; lockTable.Data.Rows[0][ColumnUserName] = _client.User.Name; try { _client.Data.Update(lockTable.Data); } catch (Exception err) { throw new Exception("Could not update checkout file: " + err.Message, err); } }
/// <summary> /// Check in the given files. /// </summary> /// <param name="files">The files to checkin.</param> public void CheckinFiles(IEnumerable <SourceFile> files) { if (files == null) { throw new ArgumentNullException("files"); } if (!IsEnabled()) { return; } List <string> ids = new List <string>(); foreach (SourceFile file in files) { ids.Add(file.Id); } DataSelectResult result = _client.Data.SelectAll(String.Format("SELECT Id FROM {0} WHERE {1} IN ('{2}')", TableName, ColumnEntityId, String.Join("','", ids))); try { _client.Data.Delete(result.Data); foreach (SourceFile file in files) { file.CheckedOutBy = null; } } catch (Exception err) { throw new Exception("Could not checkin file: " + err.Message, err); } }
/// <summary> /// Execute a select query on the server to get the next batch of data results. /// </summary> /// <param name="previousResult">The last batch of data results that were received.</param> /// <returns>The next batch of data results that follow the given previous data results.</returns> public DataSelectResult Select(DataSelectResult previousResult) { return(Select(previousResult, false)); }
/// <summary> /// Get all of the log listeners that currently exist. /// </summary> /// <returns>The currently existing log listeners.</returns> public LogListener[] GetLogListeners() { SalesForceAPI.Tooling.queryResponse response = _client.ToolingClient.query(new SalesForceAPI.Tooling.queryRequest( new SalesForceAPI.Tooling.SessionHeader() { sessionId = _client.SessionId }, String.Format("SELECT Id, ApexCode, ApexProfiling, Callout, Database, ExpirationDate, ScopeId, System, TracedEntityId, Validation, Visualforce, Workflow FROM TraceFlag WHERE ExpirationDate > {0}", DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ")))); List <LogListener> result = new List <LogListener>(); if (response.result.records != null) { // get entity names Dictionary <string, string> names = new Dictionary <string, string>(); foreach (SalesForceAPI.Tooling.TraceFlag traceFlag in response.result.records) { if (traceFlag.ScopeId != null && !names.ContainsKey(traceFlag.ScopeId)) { names.Add(traceFlag.ScopeId, String.Empty); } if (traceFlag.TracedEntityId != null && !names.ContainsKey(traceFlag.TracedEntityId)) { names.Add(traceFlag.TracedEntityId, String.Empty); } } DataSelectResult nameData = _client.Data.SelectAll(String.Format( "SELECT Id, Name FROM User WHERE Id IN ('{0}')", String.Join("','", names.Keys))); foreach (DataRow row in nameData.Data.Rows) { names[row["Id"] as string] = String.Format("{0} (user)", row["Name"]); } nameData = _client.Data.SelectAll(String.Format( "SELECT Id, Name FROM ApexClass WHERE Id IN ('{0}')", String.Join("','", names.Keys))); foreach (DataRow row in nameData.Data.Rows) { names[row["Id"] as string] = String.Format("{0} (class)", row["Name"]); } nameData = _client.Data.SelectAll(String.Format( "SELECT Id, Name FROM ApexTrigger WHERE Id IN ('{0}')", String.Join("','", names.Keys))); foreach (DataRow row in nameData.Data.Rows) { names[row["Id"] as string] = String.Format("{0} (trigger)", row["Name"]); } // create log listener foreach (SalesForceAPI.Tooling.TraceFlag traceFlag in response.result.records) { result.Add(new LogListener( traceFlag, (traceFlag.ScopeId != null) ? names[traceFlag.ScopeId] : String.Empty, (traceFlag.TracedEntityId != null) ? names[traceFlag.TracedEntityId] : String.Empty)); } } return(result.ToArray()); }
/// <summary> /// Update the test run with the most recent status. /// </summary> /// <param name="testRun">The test run to update.</param> public void UpdateTests(TestRun testRun) { // filter out completed test runs Dictionary <string, TestRunItem> itemsMap = new Dictionary <string, TestRunItem>(); StringBuilder classIdBuilder = new StringBuilder(); foreach (TestRunItem item in testRun.Items) { if (!item.IsDone) { itemsMap.Add(item.ApexClassId, item); classIdBuilder.AppendFormat("'{0}',", item.ApexClassId); } } // get updated status StringBuilder completedItemsBuilder = new StringBuilder(); if (itemsMap.Count > 0) { classIdBuilder.Length--; DataSelectResult testRunData = _client.Data.SelectAll(String.Format( "SELECT ApexClassId, Status, ExtendedStatus FROM ApexTestQueueItem WHERE ParentJobId = '{0}' AND ApexClassId IN ({1})", testRun.JobId, classIdBuilder.ToString())); foreach (DataRow row in testRunData.Data.Rows) { TestRunItem item = itemsMap[row["ApexClassId"] as string]; item.Status = (TestRunItemStatus)Enum.Parse(typeof(TestRunItemStatus), row["Status"] as string); item.ExtendedStatus = row["ExtendedStatus"] as string; if (item.IsDone) { completedItemsBuilder.AppendFormat("'{0}',", item.ApexClassId); } } } // get details for items that were completed if (completedItemsBuilder.Length > 0) { completedItemsBuilder.Length--; DataSelectResult completedData = _client.Data.SelectAll( String.Format("SELECT ApexClassId, ApexLogId, Message, MethodName, Outcome, StackTrace FROM ApexTestResult WHERE AsyncApexJobId = '{0}' AND ApexClassId IN ({1})", testRun.JobId, completedItemsBuilder.ToString())); Dictionary <string, List <TestRunItemResult> > resultsMap = new Dictionary <string, List <TestRunItemResult> >(); foreach (DataRow row in completedData.Data.Rows) { List <TestRunItemResult> resultList = null; string classId = row["ApexClassId"] as string; if (resultsMap.ContainsKey(classId)) { resultList = resultsMap[classId]; } else { resultList = new List <TestRunItemResult>(); resultsMap.Add(classId, resultList); } resultList.Add(new TestRunItemResult( row["Message"] as string, row["MethodName"] as string, (TestRunItemResultStatus)Enum.Parse(typeof(TestRunItemResultStatus), row["Outcome"] as string), row["StackTrace"] as string)); } foreach (KeyValuePair <string, List <TestRunItemResult> > kvp in resultsMap) { itemsMap[kvp.Key].Results = kvp.Value.OrderBy(t => t.MethodName).ToArray(); } } if (testRun.IsDone) { testRun.Finished = DateTime.Now; } }