Implement with_tasks option to expand the task information (#3343)

* Implement with_tasks option to expand the task information

* Added tests

* format
This commit is contained in:
Cheick Keita
2023-07-26 16:00:53 -07:00
committed by GitHub
parent fc4e698f9b
commit 8cb761a565
7 changed files with 88 additions and 22 deletions

View File

@ -135,10 +135,14 @@ public class Jobs {
static JobTaskInfo TaskToJobTaskInfo(Task t) => new(t.TaskId, t.Config.Task.Type, t.State); static JobTaskInfo TaskToJobTaskInfo(Task t) => new(t.TaskId, t.Config.Task.Type, t.State);
// TODO: search.WithTasks is not checked in Python code? var tasks = _context.TaskOperations.SearchStates(jobId);
if (search.WithTasks ?? false) {
var taskInfo = await _context.TaskOperations.SearchStates(jobId).Select(TaskToJobTaskInfo).ToListAsync(); var ts = await tasks.ToListAsync();
return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo)); return await RequestHandling.Ok(req, JobResponse.ForJob(job, ts));
} else {
var taskInfo = await tasks.Select(TaskToJobTaskInfo).ToListAsync();
return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo));
}
} }
var jobs = await _context.JobOperations.SearchState(states: search.State ?? Enumerable.Empty<JobState>()).ToListAsync(); var jobs = await _context.JobOperations.SearchState(states: search.State ?? Enumerable.Empty<JobState>()).ToListAsync();

View File

@ -282,7 +282,8 @@ public record Task(
ISecret<Authentication>? Auth = null, ISecret<Authentication>? Auth = null,
DateTimeOffset? Heartbeat = null, DateTimeOffset? Heartbeat = null,
DateTimeOffset? EndTime = null, DateTimeOffset? EndTime = null,
StoredUserInfo? UserInfo = null) : StatefulEntityBase<TaskState>(State) { StoredUserInfo? UserInfo = null) : StatefulEntityBase<TaskState>(State), IJobTaskInfo {
public TaskType Type => Config.Task.Type;
} }
public record TaskEvent( public record TaskEvent(
@ -898,11 +899,19 @@ public record JobConfig(
} }
} }
[JsonDerivedType(typeof(Task), typeDiscriminator: "Task")]
[JsonDerivedType(typeof(JobTaskInfo), typeDiscriminator: "JobTaskInfo")]
public interface IJobTaskInfo {
Guid TaskId { get; }
TaskType Type { get; }
TaskState State { get; }
}
public record JobTaskInfo( public record JobTaskInfo(
Guid TaskId, Guid TaskId,
TaskType Type, TaskType Type,
TaskState State TaskState State
); ) : IJobTaskInfo;
public record Job( public record Job(
[PartitionKey][RowKey] Guid JobId, [PartitionKey][RowKey] Guid JobId,

View File

@ -96,13 +96,13 @@ public record JobResponse(
JobConfig Config, JobConfig Config,
string? Error, string? Error,
DateTimeOffset? EndTime, DateTimeOffset? EndTime,
List<JobTaskInfo>? TaskInfo, IEnumerable<IJobTaskInfo>? TaskInfo,
StoredUserInfo? UserInfo, StoredUserInfo? UserInfo,
[property: JsonPropertyName("Timestamp")] // must retain capital T for backcompat [property: JsonPropertyName("Timestamp")] // must retain capital T for backcompat
DateTimeOffset? Timestamp DateTimeOffset? Timestamp
// not including UserInfo from Job model // not including UserInfo from Job model
) : BaseResponse() { ) : BaseResponse() {
public static JobResponse ForJob(Job j, List<JobTaskInfo>? taskInfo) public static JobResponse ForJob(Job j, IEnumerable<IJobTaskInfo>? taskInfo)
=> new( => new(
JobId: j.JobId, JobId: j.JobId,
State: j.State, State: j.State,

View File

@ -175,4 +175,55 @@ public abstract class JobsTestBase : FunctionTestBase {
var metadata = Assert.Single(container.Value); var metadata = Assert.Single(container.Value);
Assert.Equal(new KeyValuePair<string, string>("container_type", "logs"), metadata); Assert.Equal(new KeyValuePair<string, string>("container_type", "logs"), metadata);
} }
[Fact]
public async Async.Task Get_CanFindSpecificJobWithTaskInfo() {
var taskConfig = new TaskConfig(_jobId, new List<Guid>(), new TaskDetails(TaskType.Coverage, 60));
var task = new Task(_jobId, Guid.NewGuid(), TaskState.Running, Os.Windows, taskConfig);
await Context.InsertAll(
new Job(_jobId, JobState.Stopped, _config, null), task);
var func = new Jobs(Context, LoggerProvider.CreateLogger<Jobs>());
var ctx = new TestFunctionContext();
var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId, WithTasks: false)), ctx);
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
var response = BodyAs<JobResponse>(result);
Assert.Equal(_jobId, response.JobId);
Assert.NotNull(response.TaskInfo);
var returnedTasks = response.TaskInfo.OfType<JobTaskInfo>().ToList();
Assert.NotEmpty(returnedTasks);
Assert.Equal(task.TaskId, returnedTasks[0].TaskId);
Assert.Equal(task.State, returnedTasks[0].State);
Assert.Equal(task.Config.Task.Type, returnedTasks[0].Type);
}
[Fact]
public async Async.Task Get_CanFindSpecificJobWithFullTask() {
var taskConfig = new TaskConfig(_jobId, new List<Guid>(), new TaskDetails(TaskType.Coverage, 60));
var task = new Task(_jobId, Guid.NewGuid(), TaskState.Running, Os.Windows, taskConfig);
await Context.InsertAll(
new Job(_jobId, JobState.Stopped, _config, null), task);
var func = new Jobs(Context, LoggerProvider.CreateLogger<Jobs>());
var ctx = new TestFunctionContext();
var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId, WithTasks: true)), ctx);
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
var response = BodyAs<JobResponse>(result);
Assert.Equal(_jobId, response.JobId);
Assert.NotNull(response.TaskInfo);
var returnedTasks = response.TaskInfo.OfType<Task>().ToList();
Assert.NotEmpty(returnedTasks);
Assert.Equal(task.TaskId, returnedTasks[0].TaskId);
Assert.Equal(task.State, returnedTasks[0].State);
Assert.Equal(task.Config.Task.Type, returnedTasks[0].Type);
}
} }

View File

@ -1308,14 +1308,16 @@ class Jobs(Endpoint):
"DELETE", models.Job, data=requests.JobGet(job_id=job_id_expanded) "DELETE", models.Job, data=requests.JobGet(job_id=job_id_expanded)
) )
def get(self, job_id: UUID_EXPANSION) -> models.Job: def get(self, job_id: UUID_EXPANSION, with_tasks: bool = False) -> models.Job:
"""Get information about a specific job""" """Get information about a specific job"""
job_id_expanded = self._disambiguate_uuid( job_id_expanded = self._disambiguate_uuid(
"job_id", job_id, lambda: [str(x.job_id) for x in self.list()] "job_id", job_id, lambda: [str(x.job_id) for x in self.list()]
) )
self.logger.debug("get job: %s", job_id_expanded) self.logger.debug("get job: %s", job_id_expanded)
job = self._req_model( job = self._req_model(
"GET", models.Job, data=requests.JobGet(job_id=job_id_expanded) "GET",
models.Job,
data=requests.JobGet(job_id=job_id_expanded, with_tasks=with_tasks),
) )
return job return job

View File

@ -463,17 +463,6 @@ class JobTaskInfo(BaseModel):
state: TaskState state: TaskState
class Job(BaseModel):
timestamp: Optional[datetime] = Field(alias="Timestamp")
job_id: UUID = Field(default_factory=uuid4)
state: JobState = Field(default=JobState.init)
config: JobConfig
error: Optional[str]
end_time: Optional[datetime] = None
task_info: Optional[List[JobTaskInfo]]
user_info: Optional[UserInfo]
class TaskHeartbeatEntry(BaseModel): class TaskHeartbeatEntry(BaseModel):
task_id: UUID task_id: UUID
job_id: Optional[UUID] job_id: Optional[UUID]
@ -757,6 +746,17 @@ class Task(BaseModel):
user_info: Optional[UserInfo] user_info: Optional[UserInfo]
class Job(BaseModel):
timestamp: Optional[datetime] = Field(alias="Timestamp")
job_id: UUID = Field(default_factory=uuid4)
state: JobState = Field(default=JobState.init)
config: JobConfig
error: Optional[str]
end_time: Optional[datetime] = None
task_info: Optional[List[Union[Task, JobTaskInfo]]]
user_info: Optional[UserInfo]
class NetworkConfig(BaseModel): class NetworkConfig(BaseModel):
address_space: str = Field(default="10.0.0.0/8") address_space: str = Field(default="10.0.0.0/8")
subnet: str = Field(default="10.0.0.0/16") subnet: str = Field(default="10.0.0.0/16")

View File

@ -39,13 +39,13 @@ class BaseRequest(BaseModel):
class JobGet(BaseRequest): class JobGet(BaseRequest):
job_id: UUID job_id: UUID
with_tasks: Optional[bool]
class JobSearch(BaseRequest): class JobSearch(BaseRequest):
job_id: Optional[UUID] job_id: Optional[UUID]
state: Optional[List[JobState]] state: Optional[List[JobState]]
task_state: Optional[List[TaskState]] task_state: Optional[List[TaskState]]
with_tasks: Optional[bool]
class NotificationCreate(BaseRequest, NotificationConfig): class NotificationCreate(BaseRequest, NotificationConfig):