Skip to content

Commit 77ccbdd

Browse files
authored
Workflows main page refactoring - side bar (#578)
1 parent f913168 commit 77ccbdd

File tree

19 files changed

+267
-247
lines changed

19 files changed

+267
-247
lines changed

src/main/scala/za/co/absa/hyperdrive/trigger/api/rest/controllers/WorkflowController.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class WorkflowController @Inject()(workflowService: WorkflowService, generalConf
165165
}
166166

167167
@PostMapping(path = Array("/workflows/import"))
168-
def importWorkflows(@RequestPart("file") file: MultipartFile): CompletableFuture[Seq[Project]] = {
168+
def importWorkflows(@RequestPart("file") file: MultipartFile): CompletableFuture[Seq[WorkflowJoined]] = {
169169
val zipEntries = extractZipEntries(file.getBytes)
170170
if (zipEntries.isEmpty) {
171171
throw new ApiException(GenericError("The given zip file does not contain any workflows"))

src/main/scala/za/co/absa/hyperdrive/trigger/api/rest/services/WorkflowService.scala

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ trait WorkflowService {
6262

6363
def exportWorkflows(workflowIds: Seq[Long])(implicit ec: ExecutionContext): Future[Seq[WorkflowImportExportWrapper]]
6464

65-
def importWorkflows(workflowImports: Seq[WorkflowImportExportWrapper])(implicit ec: ExecutionContext): Future[Seq[Project]]
65+
def importWorkflows(workflowImports: Seq[WorkflowImportExportWrapper])(implicit ec: ExecutionContext): Future[Seq[WorkflowJoined]]
6666

6767
def convertToWorkflowJoined(workflowImport: WorkflowImportExportWrapper)(implicit ec: ExecutionContext): Future[WorkflowJoined]
6868
}
@@ -151,15 +151,11 @@ class WorkflowServiceImpl(override val workflowRepository: WorkflowRepository,
151151
}
152152

153153
override def getProjectNames()(implicit ec: ExecutionContext): Future[Set[String]] = {
154-
workflowRepository.getProjects().map(_.toSet)
154+
workflowRepository.getProjectNames().map(_.toSet)
155155
}
156156

157157
override def getProjects()(implicit ec: ExecutionContext): Future[Seq[Project]] = {
158-
workflowRepository.getWorkflows().map { workflows =>
159-
workflows.groupBy(_.project).map {
160-
case (projectName, workflows) => Project(projectName, workflows)
161-
}.toSeq
162-
}
158+
workflowRepository.getProjects()
163159
}
164160

165161
override def getProjectsInfo()(implicit ec: ExecutionContext): Future[Seq[ProjectInfo]] = {
@@ -229,16 +225,16 @@ class WorkflowServiceImpl(override val workflowRepository: WorkflowRepository,
229225
}
230226
}
231227

232-
override def importWorkflows(workflowImports: Seq[WorkflowImportExportWrapper])(implicit ec: ExecutionContext): Future[Seq[Project]] = {
228+
override def importWorkflows(workflowImports: Seq[WorkflowImportExportWrapper])(implicit ec: ExecutionContext): Future[Seq[WorkflowJoined]] = {
233229
val userName = getUserName.apply()
234230
for {
235231
workflowJoineds <- convertToWorkflowJoineds(workflowImports)
236232
deactivatedWorkflows = workflowJoineds.map(workflowJoined => workflowJoined.copy(isActive = false))
237233
_ <- workflowValidationService.validateOnInsert(deactivatedWorkflows)
238-
_ <- workflowRepository.insertWorkflows(deactivatedWorkflows, userName)
239-
projects <- getProjects()
234+
workflowIds <- workflowRepository.insertWorkflows(deactivatedWorkflows, userName)
235+
workflows <- workflowRepository.getWorkflows(workflowIds)
240236
} yield {
241-
projects
237+
workflows
242238
}
243239
}
244240

src/main/scala/za/co/absa/hyperdrive/trigger/models/Project.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,10 @@ package za.co.absa.hyperdrive.trigger.models
1717

1818
case class Project(
1919
name: String,
20-
workflows: Seq[Workflow]
20+
workflows: Seq[WorkflowIdentity]
21+
)
22+
23+
case class WorkflowIdentity(
24+
id: Long,
25+
name: String
2126
)

src/main/scala/za/co/absa/hyperdrive/trigger/persistance/WorkflowRepository.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ trait WorkflowRepository extends Repository {
4646
def updateWorkflow(workflow: WorkflowJoined, user: String)(implicit ec: ExecutionContext): Future[Unit]
4747
def switchWorkflowActiveState(id: Long, user: String)(implicit ec: ExecutionContext): Future[Unit]
4848
def updateWorkflowsIsActive(ids: Seq[Long], isActiveNewValue: Boolean, user: String)(implicit ec: ExecutionContext): Future[Unit]
49-
def getProjects()(implicit ec: ExecutionContext): Future[Seq[String]]
49+
def getProjects()(implicit ec: ExecutionContext): Future[Seq[Project]]
50+
def getProjectNames()(implicit ec: ExecutionContext): Future[Seq[String]]
5051
def getProjectsInfo()(implicit ec: ExecutionContext): Future[Seq[ProjectInfo]]
5152
def existsProject(project: String)(implicit ec: ExecutionContext): Future[Boolean]
5253
def releaseWorkflowAssignmentsOfDeactivatedInstances()(implicit ec: ExecutionContext): Future[(Int, Int)]
@@ -174,7 +175,7 @@ class WorkflowRepositoryImpl @Inject()(
174175
}
175176

176177
override def getWorkflows()(implicit ec: ExecutionContext): Future[Seq[Workflow]] = db.run(
177-
workflowTable.sortBy(_.name).result
178+
workflowTable.sortBy(workflow => (workflow.project, workflow.name)).result
178179
)
179180

180181
override def searchWorkflows(searchRequest: TableSearchRequest)(implicit ec: ExecutionContext): Future[TableSearchResponse[Workflow]] = {
@@ -282,7 +283,18 @@ class WorkflowRepositoryImpl @Inject()(
282283
)
283284
}
284285

285-
override def getProjects()(implicit ec: ExecutionContext): Future[Seq[String]] = db.run(
286+
override def getProjects()(implicit ec: ExecutionContext): Future[Seq[Project]] = {
287+
db.run(
288+
workflowTable.map(workflow => (workflow.project, workflow.name, workflow.id)).result
289+
).map(_.groupBy(_._1).map { case (project, workflows) =>
290+
val workflowIdentities = workflows.map { case (_, name, id) =>
291+
WorkflowIdentity(id, name)
292+
}
293+
Project(project, workflowIdentities)
294+
}.toSeq)
295+
}
296+
297+
override def getProjectNames()(implicit ec: ExecutionContext): Future[Seq[String]] = db.run(
286298
workflowTable.map(_.project).distinct.sortBy(_.value).result
287299
)
288300

src/test/scala/za/co/absa/hyperdrive/trigger/api/rest/WorkflowControllerTest.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,14 @@ class WorkflowControllerTest extends AsyncFlatSpec with Matchers with MockitoSug
121121
val byteArray = createZip(zipEntries)
122122
val zip = new MockMultipartFile("the.zip", byteArray)
123123

124-
val projects = Seq(
125-
Project(w1.project, Seq(w1.toWorkflow)),
126-
Project(w2.project, Seq(w2.toWorkflow))
127-
)
128-
when(workflowService.importWorkflows(any())(any())).thenReturn(Future { projects })
124+
val workflows = Seq(w1, w2)
125+
when(workflowService.importWorkflows(any())(any())).thenReturn(Future { workflows })
129126

130127
// when
131128
val result = underTest.importWorkflows(zip).get()
132129

133130
// then
134-
result shouldBe projects
131+
result shouldBe workflows
135132
val workflowWrappersCaptor: ArgumentCaptor[Seq[WorkflowImportExportWrapper]] =
136133
ArgumentCaptor.forClass(classOf[Seq[WorkflowImportExportWrapper]])
137134
verify(workflowService).importWorkflows(workflowWrappersCaptor.capture())(any())

src/test/scala/za/co/absa/hyperdrive/trigger/api/rest/services/WorkflowServiceTest.scala

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import org.scalatest.{AsyncFlatSpec, BeforeAndAfter, Matchers}
2424
import za.co.absa.hyperdrive.trigger.TestUtils.await
2525
import za.co.absa.hyperdrive.trigger.configuration.application.TestGeneralConfig
2626
import za.co.absa.hyperdrive.trigger.models._
27-
import za.co.absa.hyperdrive.trigger.models.enums.{DagInstanceStatuses, JobTypes}
27+
import za.co.absa.hyperdrive.trigger.models.enums.DagInstanceStatuses
2828
import za.co.absa.hyperdrive.trigger.models.errors.ApiErrorTypes.{BulkOperationErrorType, GenericErrorType}
2929
import za.co.absa.hyperdrive.trigger.models.errors.{ApiError, ApiException, DatabaseError, ValidationError}
3030
import za.co.absa.hyperdrive.trigger.persistance.{DagInstanceRepository, WorkflowRepository}
@@ -157,43 +157,41 @@ class WorkflowServiceTest extends AsyncFlatSpec with Matchers with MockitoSugar
157157

158158
"WorkflowService.getProjects" should "should return no project on no workflows" in {
159159
// given
160-
when(workflowRepository.getWorkflows()(any[ExecutionContext])).thenReturn(Future{Seq()})
160+
when(workflowRepository.getProjects()(any[ExecutionContext])).thenReturn(Future{Seq()})
161161

162162
// when
163163
val result: Seq[Project] = await(underTest.getProjects())
164164

165165
// then
166-
verify(workflowRepository, times(1)).getWorkflows()
166+
verify(workflowRepository, times(1)).getProjects()
167167
result shouldBe Seq.empty[Project]
168168
}
169169

170170
"WorkflowService.getProjects" should "should return projects on some workflows" in {
171171
// given
172-
val workflows = Seq(
173-
Workflow(
174-
name = "workflowA",
175-
isActive = true,
176-
project = "projectA",
177-
created = LocalDateTime.now(),
178-
updated = None,
179-
id = 0
172+
val projects = Seq(
173+
Project(
174+
"projectA",
175+
Seq(WorkflowIdentity(
176+
0,
177+
"workflowA"
178+
))
180179
),
181-
Workflow(
182-
name = "workflowB",
183-
isActive = false,
184-
project = "projectB",
185-
created = LocalDateTime.now(),
186-
updated = None,
187-
id = 1
180+
Project(
181+
"projectB",
182+
Seq(WorkflowIdentity(
183+
1,
184+
"workflowB"
185+
))
188186
)
189187
)
190-
when(workflowRepository.getWorkflows()(any[ExecutionContext])).thenReturn(Future{workflows})
188+
when(workflowRepository.getProjects()(any[ExecutionContext])).thenReturn(Future{projects})
191189

192190
// when
193191
val result: Seq[Project] = await(underTest.getProjects())
194192

195193
// then
196-
verify(workflowRepository, times(1)).getWorkflows()
194+
verify(workflowRepository, times(1)).getProjects()
197195
result.length shouldBe 2
198196
}
199197

@@ -309,6 +307,8 @@ class WorkflowServiceTest extends AsyncFlatSpec with Matchers with MockitoSugar
309307
// given
310308
val workflowJoined1 = WorkflowFixture.createWorkflowJoined().copy(schedulerInstanceId = Some(42))
311309
val workflowJoined2 = WorkflowFixture.createTimeBasedShellScriptWorkflow("project").copy()
310+
val workflowIds = Seq(21L, 22L)
311+
312312
val jobTemplates1 = Seq(JobTemplateFixture.GenericSparkJobTemplate, JobTemplateFixture.GenericShellJobTemplate)
313313
val jobTemplates2 = Seq(JobTemplateFixture.GenericShellJobTemplate)
314314
val workflowImports = Seq(
@@ -321,21 +321,17 @@ class WorkflowServiceTest extends AsyncFlatSpec with Matchers with MockitoSugar
321321
)
322322
val newJobTemplatesIdMap = newJobTemplates.map(t => t.name -> t.id).toMap
323323

324-
val workflows = workflowImports.map(_.workflowJoined.toWorkflow)
325-
val expectedProjects = Seq(
326-
Project(workflowJoined1.project, Seq(workflowJoined1.toWorkflow)),
327-
Project(workflowJoined2.project, Seq(workflowJoined2.toWorkflow))
328-
)
324+
val expectedWorkflows = Seq(workflowJoined1, workflowJoined2)
329325
when(jobTemplateService.getJobTemplateIdsByNames(any())(any[ExecutionContext])).thenReturn(Future{newJobTemplatesIdMap})
330326
when(workflowValidationService.validateOnInsert(any[Seq[WorkflowJoined]])(any())).thenReturn(Future{})
331-
when(workflowRepository.insertWorkflows(any(), any())(any())).thenReturn(Future { Seq(21L, 22L) })
332-
when(workflowRepository.getWorkflows()(any[ExecutionContext])).thenReturn(Future{workflows})
327+
when(workflowRepository.insertWorkflows(any(), any())(any())).thenReturn(Future { workflowIds })
328+
when(workflowRepository.getWorkflows(eqTo(workflowIds))(any[ExecutionContext])).thenReturn(Future{expectedWorkflows})
333329

334330
// when
335331
val actualProjects = await(underTest.importWorkflows(workflowImports))
336332

337333
// then
338-
actualProjects should contain theSameElementsAs expectedProjects
334+
actualProjects should contain theSameElementsAs expectedWorkflows
339335
val stringsCaptor: ArgumentCaptor[Seq[String]] = ArgumentCaptor.forClass(classOf[Seq[String]])
340336
verify(jobTemplateService).getJobTemplateIdsByNames(stringsCaptor.capture())(any())
341337
stringsCaptor.getValue should contain theSameElementsAs workflowImports.flatMap(_.jobTemplates.map(_.name)).distinct

src/test/scala/za/co/absa/hyperdrive/trigger/persistance/WorkflowRepositoryPostgresTest.scala

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import org.mockito.ArgumentMatchers.any
2020
import org.mockito.Mockito._
2121
import org.scalatest.mockito.MockitoSugar
2222
import org.scalatest.{FlatSpec, _}
23-
import za.co.absa.hyperdrive.trigger.models.Workflow
23+
import za.co.absa.hyperdrive.trigger.models.{Project, Workflow, WorkflowIdentity}
2424
import za.co.absa.hyperdrive.trigger.models.enums.SchedulerInstanceStatuses
2525
import za.co.absa.hyperdrive.trigger.models.errors.{ApiException, GenericDatabaseError}
2626
import za.co.absa.hyperdrive.trigger.models.search.{BooleanFilterAttributes, BooleanValues, SortAttributes, TableSearchRequest, TableSearchResponse}
@@ -588,6 +588,20 @@ class WorkflowRepositoryPostgresTest extends FlatSpec with Matchers with BeforeA
588588
await(workflowRepository.getMaxWorkflowId) shouldBe None
589589
}
590590

591+
"getProjects()" should "return workflow identities grouped by project name" in {
592+
// given
593+
createTestData()
594+
val expected = TestData.workflows.groupBy(_.project).map{ case (project, workflows) =>
595+
Project(project, workflows.map(workflow => WorkflowIdentity(workflow.id, workflow.name)))
596+
}
597+
598+
// when
599+
val result = await(workflowRepository.getProjects())
600+
601+
// then
602+
result shouldBe expected
603+
}
604+
591605
"searchWorkflows" should "return zero workflows when db is empty" in {
592606
val searchRequest: TableSearchRequest = TableSearchRequest(
593607
sort = None,

ui/src/app/components/workflows/workflows-home/workflows-home.component.spec.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,9 @@ describe('WorkflowsHomeComponent', () => {
4747

4848
const initialAppState = {
4949
workflows: {
50-
projects: [
51-
ProjectModelFactory.create('projectOne', [
52-
WorkflowModelFactory.create('workflowOne', undefined, undefined, undefined, undefined, undefined),
53-
]),
54-
ProjectModelFactory.create('projectTwo', [
55-
WorkflowModelFactory.create('workflowTwo', undefined, undefined, undefined, undefined, undefined),
56-
]),
50+
workflows: [
51+
WorkflowModelFactory.create('workflowOne', undefined, undefined, undefined, undefined, undefined),
52+
WorkflowModelFactory.create('workflowTwo', undefined, undefined, undefined, undefined, undefined),
5753
],
5854
},
5955
workflowsSort: undefined,
@@ -87,7 +83,7 @@ describe('WorkflowsHomeComponent', () => {
8783
waitForAsync(() => {
8884
fixture.detectChanges();
8985
fixture.whenStable().then(() => {
90-
expect(underTest.workflows).toEqual([].concat(...initialAppState.workflows.projects.map((project) => project.workflows)));
86+
expect(underTest.workflows).toEqual([...initialAppState.workflows.workflows]);
9187
expect(underTest.sort).toEqual(initialAppState.workflowsSort);
9288
expect(underTest.filters).toBeUndefined();
9389
});

ui/src/app/components/workflows/workflows-home/workflows-home.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class WorkflowsHomeComponent implements OnInit, OnDestroy {
7474

7575
ngOnInit(): void {
7676
this.workflowsSubscription = this.store.select(selectWorkflowState).subscribe((state) => {
77-
this.workflows = [].concat(...state.projects.map((project) => project.workflows));
77+
this.workflows = state.workflows;
7878
this.sort = state.workflowsSort;
7979
this.filters = state.workflowsFilters;
8080
});

ui/src/app/constants/api.constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const api = {
2424
KILL_JOB: '/jobInstances/{applicationId}/kill',
2525

2626
GET_PROJECTS: '/workflows/projects',
27+
GET_WORKFLOWS: '/workflows',
2728
GET_WORKFLOW: '/workflow',
2829
DELETE_WORKFLOW: '/workflows',
2930
EXPORT_WORKFLOWS: '/workflows/export',

0 commit comments

Comments
 (0)