Skip to content

Commit 1655daf

Browse files
authored
Add support for v2/jobs/get-errors API (#819)
1 parent 2b1926a commit 1655daf

14 files changed

+305
-11
lines changed

Examples.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ public async Task LoginWithClientCredentialsAndMonitorClientQuota()
260260
- [2.1. Update Default Token Quota at Tenant level](#21-update-default-token-quota-at-tenant-level)
261261
- [2.2 Update Token Quota at Client level](#22-update-token-quota-at-client-level)
262262
- [2.2 Update Token Quota at Organisation level](#23-update-token-quota-at-organisation-level)
263+
- [3. Get Job Error Details](#3-get-job-error-details)
263264

264265
## 1. Management Client Initialization
265266

@@ -368,3 +369,36 @@ Assuming you have an access token available with the required scopes.
368369

369370
```
370371
⬆️ [Go to Top](#)
372+
373+
## 3. Get Job Error Details
374+
375+
When a job fails, you can get the error details using the `GetErrorDetailsAsync` method.
376+
The response (of type `JobError`) will either contain `JobImportErrorDetails[]` or `JobErrorDetails` depending on the type of job.
377+
378+
Assuming you have an access token available with the required scopes and the `apiClient` is initialized as shown in the previous sections.
379+
```csharp
380+
381+
public async void GetJobErrorDetails(string jobId) {
382+
383+
var jobId = "your_job_id";
384+
var jobError = await apiClient.Jobs.GetErrorDetailsAsync(jobId);
385+
386+
Console.WriteLine($"Job ID: {jobId}");
387+
Console.WriteLine($"Job Type: {jobError.JobErrorDetails.Type}");
388+
Console.WriteLine($"Job Status: {jobError.JobErrorDetails.Status}");
389+
Console.WriteLine($"Job Id: {jobError.JobErrorDetails.Id}");
390+
Console.WriteLine($"Job Connection: {jobError.JobErrorDetails.Connection}");
391+
Console.WriteLine($"Job Connection Id: {jobError.JobErrorDetails.ConnectionId}");
392+
Console.WriteLine($"Job Created At: {jobError.JobErrorDetails.CreatedAt}");
393+
Console.WriteLine($"Job Status Details: {jobError.JobErrorDetails.StatusDetails}");
394+
395+
// OR
396+
Console.WriteLine($"Job User object: {jobError.JobImportErrorDetails[0].User}");
397+
Console.WriteLine($"Job Error Code: {jobError.JobImportErrorDetails[0].Errors[0].Code}");
398+
Console.WriteLine($"Job Error Code: {jobError.JobImportErrorDetails[0].Errors[0].Message}");
399+
Console.WriteLine($"Job Error Code: {jobError.JobImportErrorDetails[0].Errors[0].Path}");
400+
}
401+
402+
403+
```
404+
⬆️ [Go to Top](#)

src/Auth0.ManagementApi/Clients/IJobsClient.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,13 @@ public interface IJobsClient
5252
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
5353
/// <returns>A <see cref="Job"/> instance containing the information about the job.</returns>
5454
Task<Job> SendVerificationEmailAsync(VerifyEmailJobRequest request, CancellationToken cancellationToken = default);
55+
56+
/// <summary>
57+
/// Retrieve error details of a failed job.
58+
/// </summary>
59+
/// <param name="id">The ID of the job.</param>
60+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to cancel operation.</param>
61+
/// <returns><see cref="JobError"/></returns>
62+
Task<JobError> GetErrorDetailsAsync(string id, CancellationToken cancellationToken = default);
5563
}
5664
}

src/Auth0.ManagementApi/Clients/JobsClient.cs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Net.Http;
66
using System.Threading;
77
using System.Threading.Tasks;
8+
using Newtonsoft.Json;
89

910
namespace Auth0.ManagementApi.Clients
1011
{
@@ -35,7 +36,8 @@ public JobsClient(IManagementConnection connection, Uri baseUri, IDictionary<str
3536
/// <returns>A <see cref="Job"/> instance containing the information about the job.</returns>
3637
public Task<Job> GetAsync(string id, CancellationToken cancellationToken = default)
3738
{
38-
return Connection.GetAsync<Job>(BuildUri($"jobs/{EncodePath(id)}"), DefaultHeaders, cancellationToken: cancellationToken);
39+
return Connection.GetAsync<Job>(BuildUri($"jobs/{EncodePath(id)}"), DefaultHeaders,
40+
cancellationToken: cancellationToken);
3941
}
4042

4143
/// <summary>
@@ -52,7 +54,8 @@ public Task<Job> GetAsync(string id, CancellationToken cancellationToken = defau
5254
/// <param name="sendCompletionEmail">Whether to send the user an email on import completion (true) or not (false).</param>
5355
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
5456
/// <returns>A <see cref="Job"/> instance containing the information about the job.</returns>
55-
public Task<Job> ImportUsersAsync(string connectionId, string fileName, Stream file, bool? upsert = null, string externalId = null, bool? sendCompletionEmail = null, CancellationToken cancellationToken = default)
57+
public Task<Job> ImportUsersAsync(string connectionId, string fileName, Stream file, bool? upsert = null,
58+
string externalId = null, bool? sendCompletionEmail = null, CancellationToken cancellationToken = default)
5659
{
5760
if (file == null)
5861
throw new ArgumentNullException(nameof(file));
@@ -75,7 +78,8 @@ public Task<Job> ImportUsersAsync(string connectionId, string fileName, Stream f
7578
}
7679
};
7780

78-
return Connection.SendAsync<Job>(HttpMethod.Post, BuildUri("jobs/users-imports"), parameters, DefaultHeaders, files: fileParameters, cancellationToken: cancellationToken);
81+
return Connection.SendAsync<Job>(HttpMethod.Post, BuildUri("jobs/users-imports"), parameters,
82+
DefaultHeaders, files: fileParameters, cancellationToken: cancellationToken);
7983
}
8084

8185
/// <summary>
@@ -89,7 +93,8 @@ public Task<Job> ImportUsersAsync(string connectionId, string fileName, Stream f
8993
/// <returns>A <see cref="Job"/> instance containing the information about the job.</returns>
9094
public Task<Job> ExportUsersAsync(UsersExportsJobRequest request, CancellationToken cancellationToken = default)
9195
{
92-
return Connection.SendAsync<Job>(HttpMethod.Post, BuildUri("jobs/users-exports"), request, DefaultHeaders, cancellationToken: cancellationToken);
96+
return Connection.SendAsync<Job>(HttpMethod.Post, BuildUri("jobs/users-exports"), request, DefaultHeaders,
97+
cancellationToken: cancellationToken);
9398
}
9499

95100
/// <summary>
@@ -98,9 +103,48 @@ public Task<Job> ExportUsersAsync(UsersExportsJobRequest request, CancellationTo
98103
/// <param name="request">The <see cref="VerifyEmailJobRequest"/> containing the information of the user whose email you want verified.</param>
99104
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
100105
/// <returns>A <see cref="Job"/> instance containing the information about the job.</returns>
101-
public Task<Job> SendVerificationEmailAsync(VerifyEmailJobRequest request, CancellationToken cancellationToken = default)
106+
public Task<Job> SendVerificationEmailAsync(VerifyEmailJobRequest request,
107+
CancellationToken cancellationToken = default)
102108
{
103-
return Connection.SendAsync<Job>(HttpMethod.Post, BuildUri("jobs/verification-email"), request, DefaultHeaders, cancellationToken: cancellationToken);
109+
return Connection.SendAsync<Job>(HttpMethod.Post, BuildUri("jobs/verification-email"), request,
110+
DefaultHeaders, cancellationToken: cancellationToken);
111+
}
112+
113+
/// <inheritdoc cref="IJobsClient.GetErrorDetailsAsync"/>>
114+
public Task<JobError> GetErrorDetailsAsync(string id, CancellationToken cancellationToken = default)
115+
{
116+
var rawResponse =
117+
Connection.GetAsync<string>(
118+
BuildUri($"jobs/{EncodePath(id)}/errors"), DefaultHeaders, cancellationToken: cancellationToken).Result;
119+
120+
if (string.IsNullOrEmpty(rawResponse)) return null;
121+
122+
try
123+
{
124+
var jobImportErrorDetails = JsonConvert.DeserializeObject<JobImportErrorDetails[]>(rawResponse);
125+
if (jobImportErrorDetails?.Length > 0)
126+
{
127+
return Task.FromResult(new JobError
128+
{
129+
JobImportErrorDetails = jobImportErrorDetails
130+
});
131+
}
132+
}
133+
catch (JsonSerializationException ex)
134+
{
135+
// ignoring the exception to try to serialize to JobErrorDetails.
136+
}
137+
138+
var jobErrorDetails = JsonConvert.DeserializeObject<JobErrorDetails>(rawResponse);
139+
if (jobErrorDetails != null)
140+
{
141+
return Task.FromResult(new JobError
142+
{
143+
JobErrorDetails = jobErrorDetails
144+
});
145+
}
146+
147+
return null;
104148
}
105149
}
106-
}
150+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Auth0.ManagementApi.Models
4+
{
5+
6+
/// <summary>
7+
/// Contains information about the Jobs error status.
8+
/// </summary>
9+
public class JobError
10+
{
11+
/// <inheritdoc cref="JobImportErrorDetails"/>
12+
public JobImportErrorDetails[] JobImportErrorDetails { get; set; }
13+
14+
/// <inheritdoc cref="JobErrorDetails"/>
15+
public JobErrorDetails JobErrorDetails { get; set; }
16+
}
17+
18+
/// <summary>
19+
/// Contains details of the Job including the status and failure reason
20+
/// </summary>
21+
public class JobErrorDetails : Job
22+
{
23+
/// <summary>
24+
/// Status details.
25+
/// </summary>
26+
[JsonProperty("status_details")]
27+
public string StatusDetails { get; set; }
28+
}
29+
30+
/// <summary>
31+
/// Contains information of the error that failed the job
32+
/// </summary>
33+
public class JobImportErrorDetails
34+
{
35+
/// <summary>
36+
/// User, as provided in the import file
37+
/// </summary>
38+
[JsonProperty("user")]
39+
public object User { get; set; }
40+
41+
/// <inheritdoc cref="Error"/>
42+
[JsonProperty("errors")]
43+
public Error[] Errors { get; set; }
44+
}
45+
46+
/// <summary>
47+
/// Errors importing the user.
48+
/// </summary>
49+
public class Error
50+
{
51+
/// <summary>
52+
/// Error code.
53+
/// </summary>
54+
[JsonProperty("code")]
55+
public string Code { get; set; }
56+
57+
/// <summary>
58+
/// Error message.
59+
/// </summary>
60+
[JsonProperty("message")]
61+
public string Message { get; set; }
62+
63+
/// <summary>
64+
/// Error field.
65+
/// </summary>
66+
[JsonProperty("path")]
67+
public string Path { get; set; }
68+
}
69+
}

tests/Auth0.ManagementApi.IntegrationTests/Auth0.ManagementApi.IntegrationTests.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,16 @@
3030
</ItemGroup>
3131

3232
<ItemGroup>
33+
<EmbeddedResource Include="Data\UsersImportInvalidFile.json">
34+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
35+
</EmbeddedResource>
3336
<EmbeddedResource Include="user-import-test.json">
3437
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3538
</EmbeddedResource>
39+
<None Remove="Data\UsersImportInvalid.json" />
40+
<EmbeddedResource Include="Data\UsersImportInvalid.json">
41+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
42+
</EmbeddedResource>
3643
</ItemGroup>
3744

3845
<ItemGroup>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"email": "john.doe1@nonexistingdomain",
4+
"email_verified": true,
5+
"app_metadata": {
6+
},
7+
"user_metadata": {
8+
}
9+
},
10+
{
11+
"email": "john.doe2@nonexistingdomain",
12+
"email_verified": true,
13+
"app_metadata": {
14+
},
15+
"user_metadata": {
16+
}
17+
}
18+
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"email": "[email protected]",
4+
"email_verified": tre,
5+
"app_metadata": {
6+
},
7+
"user_metadata": {
8+
}
9+
}
10+
]

0 commit comments

Comments
 (0)