diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs index 749e674d22..12ca9d130c 100644 --- a/src/AzureClient/AzureClient.cs +++ b/src/AzureClient/AzureClient.cs @@ -360,7 +360,7 @@ public async Task GetJobStatusAsync(IChannel channel, string jo } /// - public async Task GetJobListAsync(IChannel channel) + public async Task GetJobListAsync(IChannel channel, string filter) { if (ActiveWorkspace == null) { @@ -373,6 +373,14 @@ public async Task GetJobListAsync(IChannel channel) { channel.Stderr("No jobs found in current Azure Quantum workspace."); } + else + { + jobs = jobs.Where(job => job.Matches(filter)); + if (jobs.Count() == 0) + { + channel.Stderr($"No jobs matching \"{filter}\" found in current Azure Quantum workspace."); + } + } return jobs.ToExecutionResult(); } diff --git a/src/AzureClient/Extensions.cs b/src/AzureClient/Extensions.cs index 6e4cc96c56..87ddf1673d 100644 --- a/src/AzureClient/Extensions.cs +++ b/src/AzureClient/Extensions.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Threading.Tasks; +using Microsoft.Azure.Quantum; using Microsoft.Extensions.DependencyInjection; using Microsoft.Jupyter.Core; @@ -62,5 +63,13 @@ internal static async Task ToExecutionResult(this Task { singleton } : source is IEnumerable collection ? collection : null; + + /// + /// Determines whether the given matches the given filter. + /// + internal static bool Matches(this CloudJob job, string filter) => + (job.Id != null && job.Id.Contains(filter, StringComparison.OrdinalIgnoreCase)) || + (job.Details.Name != null && job.Details.Name.Contains(filter, StringComparison.OrdinalIgnoreCase)) || + (job.Details.Target != null && job.Details.Target.Contains(filter, StringComparison.OrdinalIgnoreCase)); } } diff --git a/src/AzureClient/IAzureClient.cs b/src/AzureClient/IAzureClient.cs index c820564c3e..b73995f9ff 100644 --- a/src/AzureClient/IAzureClient.cs +++ b/src/AzureClient/IAzureClient.cs @@ -94,8 +94,10 @@ public Task ConnectAsync(IChannel channel, /// Gets a list of all jobs in the current Azure Quantum workspace. /// /// - /// A list of all jobs in the current workspace. + /// A list of all jobs in the current workspace, optionally filtered + /// to jobs with fields containing filter using a case-insensitive + /// comparison. /// - public Task GetJobListAsync(IChannel channel); + public Task GetJobListAsync(IChannel channel, string filter); } } diff --git a/src/AzureClient/Magic/JobsMagic.cs b/src/AzureClient/Magic/JobsMagic.cs index 1ac082350c..3444a0be57 100644 --- a/src/AzureClient/Magic/JobsMagic.cs +++ b/src/AzureClient/Magic/JobsMagic.cs @@ -18,6 +18,8 @@ namespace Microsoft.Quantum.IQSharp.AzureClient /// public class JobsMagic : AzureClientMagicBase { + private const string ParameterNameFilter = "__filter__"; + /// /// Initializes a new instance of the class. /// @@ -33,7 +35,8 @@ public JobsMagic(IAzureClient azureClient) Summary = "Displays a list of jobs in the current Azure Quantum workspace.", Description = @" This magic command allows for displaying the list of jobs in the current - Azure Quantum workspace. + Azure Quantum workspace, optionally filtering the list to jobs which + have an ID, name, or target containing the provided filter parameter. The Azure Quantum workspace must previously have been initialized using the %azure.connect magic command. @@ -47,13 +50,25 @@ The Azure Quantum workspace must previously have been initialized Out[]: ``` ".Dedent(), + + @" + Print the list of jobs whose ID, name, or target contains ""MyJob"": + ``` + In []: %azure.jobs ""MyJob"" + Out[]: + ``` + ".Dedent(), }, }) {} /// - /// Lists all jobs in the active workspace. + /// Lists all jobs in the active workspace, optionally filtered by a provided parameter. /// - public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) => - await AzureClient.GetJobListAsync(channel); + public override async Task RunAsync(string input, IChannel channel, CancellationToken cancellationToken) + { + var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameFilter); + var filter = inputParameters.DecodeParameter(ParameterNameFilter, defaultValue: string.Empty); + return await AzureClient.GetJobListAsync(channel, filter); + } } } \ No newline at end of file diff --git a/src/AzureClient/Visualization/CloudJobEncoders.cs b/src/AzureClient/Visualization/CloudJobEncoders.cs index 4d8d9c8a3a..a8e66af5ce 100644 --- a/src/AzureClient/Visualization/CloudJobEncoders.cs +++ b/src/AzureClient/Visualization/CloudJobEncoders.cs @@ -42,10 +42,9 @@ internal static Table ToJupyterTable(this IEnumerable jobsLi Columns = new List<(string, Func)> { // TODO: add cloudJob.Uri after https://github.com/microsoft/qsharp-runtime/issues/236 is fixed. - ("Job ID", cloudJob => cloudJob.Id), ("Job Name", cloudJob => cloudJob.Details.Name), + ("Job ID", cloudJob => cloudJob.Id), ("Job Status", cloudJob => cloudJob.Status), - ("Provider", cloudJob => cloudJob.Details.ProviderId), ("Target", cloudJob => cloudJob.Details.Target), ("Creation Time", cloudJob => cloudJob.Details.CreationTime.ToDateTime()?.ToString() ?? string.Empty), ("Begin Execution Time", cloudJob => cloudJob.Details.BeginExecutionTime.ToDateTime()?.ToString() ?? string.Empty), diff --git a/src/Python/qsharp/azure.py b/src/Python/qsharp/azure.py index afbe53c3fb..04a7644998 100644 --- a/src/Python/qsharp/azure.py +++ b/src/Python/qsharp/azure.py @@ -130,7 +130,7 @@ def output(jobId : str = '', **params) -> Dict: if "error_code" in result: raise AzureError(result) return result -def jobs(**params) -> List[AzureJob]: - result = qsharp.client._execute_magic(f"azure.jobs", raise_on_stderr=False, **params) +def jobs(filter : str = '', **params) -> List[AzureJob]: + result = qsharp.client._execute_magic(f"azure.jobs {filter}", raise_on_stderr=False, **params) if "error_code" in result: raise AzureError(result) return [AzureJob(job) for job in result] diff --git a/src/Python/qsharp/tests/test_azure.py b/src/Python/qsharp/tests/test_azure.py index f626a759c6..613aa0b42a 100644 --- a/src/Python/qsharp/tests/test_azure.py +++ b/src/Python/qsharp/tests/test_azure.py @@ -107,3 +107,9 @@ def test_workspace_with_providers(): jobs = qsharp.azure.jobs() assert isinstance(jobs, list) assert len(jobs) == 2 + + # Check that job filtering works + jobs = qsharp.azure.jobs(job.id) + print(job.id) + assert isinstance(jobs, list) + assert len(jobs) == 1 diff --git a/src/Tests/AzureClientMagicTests.cs b/src/Tests/AzureClientMagicTests.cs index 19ebf98982..442cb75f27 100644 --- a/src/Tests/AzureClientMagicTests.cs +++ b/src/Tests/AzureClientMagicTests.cs @@ -162,6 +162,12 @@ public void TestJobsMagic() var jobsMagic = new JobsMagic(azureClient); jobsMagic.Test(string.Empty); Assert.AreEqual(AzureClientAction.GetJobList, azureClient.LastAction); + + // with arguments - should still print job status + azureClient = new MockAzureClient(); + jobsMagic = new JobsMagic(azureClient); + jobsMagic.Test($"{jobId}"); + Assert.AreEqual(AzureClientAction.GetJobList, azureClient.LastAction); } [TestMethod] @@ -255,7 +261,7 @@ public async Task GetConnectionStatusAsync(IChannel channel) return ExecuteStatus.Ok.ToExecutionResult(); } - public async Task GetJobListAsync(IChannel channel) + public async Task GetJobListAsync(IChannel channel, string filter) { LastAction = AzureClientAction.GetJobList; return ExecuteStatus.Ok.ToExecutionResult(); diff --git a/src/Tests/AzureClientTests.cs b/src/Tests/AzureClientTests.cs index 773d7a6d8a..998e01ba62 100644 --- a/src/Tests/AzureClientTests.cs +++ b/src/Tests/AzureClientTests.cs @@ -132,9 +132,13 @@ public void TestJobStatus() // invalid job ID ExpectError(AzureClientError.JobNotFound, azureClient.GetJobStatusAsync(new MockChannel(), "JOB_ID_3")); - // jobs list - var jobs = ExpectSuccess>(azureClient.GetJobListAsync(new MockChannel())); + // jobs list with no filter + var jobs = ExpectSuccess>(azureClient.GetJobListAsync(new MockChannel(), string.Empty)); Assert.AreEqual(2, jobs.Count()); + + // jobs list with filter + jobs = ExpectSuccess>(azureClient.GetJobListAsync(new MockChannel(), "JOB_ID_1")); + Assert.AreEqual(1, jobs.Count()); } [TestMethod]