This is an automated email from the ASF dual-hosted git repository. hshpak pushed a commit to branch feat/DATALAB-2811/view-list-of-all-images in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git
commit 7017c1f7ceb4c28faf38f55c6361861afb3ae87b Author: Hennadii_Shpak <[email protected]> AuthorDate: Tue May 24 12:11:44 2022 +0300 initial commit --- .../com/epam/datalab/model/exploratory/Image.java | 3 + .../backendapi/dao/ImageExploratoryDAO.java | 2 + .../backendapi/dao/ImageExploratoryDAOImpl.java | 12 ++ .../resources/ImageExploratoryResource.java | 20 ++ .../dto/{ImageInfoRecord.java => ImageFilter.java} | 38 +++- .../backendapi/resources/dto/ImageInfoRecord.java | 7 + .../resources/dto/ProjectImagesInfo.java} | 32 +-- .../service/ImageExploratoryService.java | 5 + .../service/impl/ImageExploratoryServiceImpl.java | 35 +++ .../resources/webapp/src/app/app.routing.module.ts | 32 +-- .../resources/webapp/src/app/core/core.module.ts | 7 +- .../capitalize-first-letter.pipe.ts} | 26 ++- .../{ => capitalize-first-letter-pipe}/index.ts | 17 +- .../resources/webapp/src/app/core/pipes/index.ts | 1 + .../src/app/core/services/appRouting.service.ts | 2 +- .../services/applicationServiceFacade.service.ts | 21 +- .../webapp/src/app/core/services/index.ts | 1 + .../app/core/services/user-images-page.service.ts} | 32 +-- .../src/app/core/services/userResource.service.ts | 15 +- .../reporting-grid/reporting-grid.component.ts | 15 +- .../bucket-browser/bucket-browser.component.ts | 17 +- .../src/app/resources/images/images.component.html | 240 +++++++++++++++++++++ .../src/app/resources/images/images.component.scss | 102 +++++++++ .../src/app/resources/images/images.component.ts | 135 ++++++++++++ .../src/app/resources/images/images.config.ts | 10 + .../src/app/resources/images/images.model.ts | 20 ++ .../webapp/src/app/resources/images/index.ts | 2 + .../resources-grid/resources-grid.component.ts | 9 +- .../resources-grid/resources-grid.model.ts | 7 - .../src/app/resources/resources.component.html | 28 +-- .../src/app/resources/resources.component.ts | 2 +- .../webapp/src/app/resources/resources.module.ts | 10 +- .../src/app/shared/bubble/bubble.component.ts | 29 +-- .../src/app/shared/navbar/navbar.component.html | 113 +++++----- .../src/app/shared/navbar/navbar.component.ts | 5 +- .../webapp/src/app/shared/navbar/navbar.config.ts | 25 +-- .../src/main/resources/webapp/src/styles.scss | 1 + .../resources/ImageExploratoryResourceTest.java | 16 +- .../service/impl/BillingServiceImplTest.java | 15 +- .../impl/ImageExploratoryServiceImplTest.java | 5 +- 40 files changed, 900 insertions(+), 214 deletions(-) diff --git a/services/datalab-model/src/main/java/com/epam/datalab/model/exploratory/Image.java b/services/datalab-model/src/main/java/com/epam/datalab/model/exploratory/Image.java index c8d3b2c35..322d5511a 100644 --- a/services/datalab-model/src/main/java/com/epam/datalab/model/exploratory/Image.java +++ b/services/datalab-model/src/main/java/com/epam/datalab/model/exploratory/Image.java @@ -24,6 +24,7 @@ import com.epam.datalab.model.library.Library; import lombok.Builder; import lombok.Data; +import java.time.Instant; import java.util.List; import java.util.Map; @@ -40,6 +41,8 @@ public class Image { private final String fullName; private final String externalName; private final String application; + private final String instanceName; + private final String cloudProvider; private final String dockerImage; private final List<Library> libraries; private final Map<String, List<Library>> computationalLibraries; diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAO.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAO.java index d8e89874a..4d97437e6 100644 --- a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAO.java +++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAO.java @@ -38,6 +38,8 @@ public interface ImageExploratoryDAO { List<ImageInfoRecord> getImages(String user, String dockerImage, String project, String endpoint, ImageStatus... statuses); + List<ImageInfoRecord> getImagesOfUser(String user, String project); + List<ImageInfoRecord> getImagesForProject(String project); Optional<ImageInfoRecord> getImage(String user, String name, String project, String endpoint); diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAOImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAOImpl.java index 234a15e52..95f4e0a1a 100644 --- a/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAOImpl.java +++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/dao/ImageExploratoryDAOImpl.java @@ -54,6 +54,7 @@ public class ImageExploratoryDAOImpl extends BaseDAO implements ImageExploratory private static final String DOCKER_IMAGE = "dockerImage"; private static final String PROJECT = "project"; private static final String ENDPOINT = "endpoint"; + private static final String CREATION_DATE = "creationDate"; @Override public boolean exist(String image, String project) { @@ -79,6 +80,13 @@ public class ImageExploratoryDAOImpl extends BaseDAO implements ImageExploratory ImageInfoRecord.class); } + @Override + public List<ImageInfoRecord> getImagesOfUser(String user, String project) { + return find(MongoCollections.IMAGES, + imageUserProjectCondition(user, project), + ImageInfoRecord.class); + } + @Override public List<ImageInfoRecord> getImagesForProject(String project) { return find(MongoCollections.IMAGES, @@ -146,6 +154,10 @@ public class ImageExploratoryDAOImpl extends BaseDAO implements ImageExploratory return and(eq(IMAGE_NAME, image), eq(PROJECT, project)); } + private Bson imageUserProjectCondition(String user, String project) { + return and(eq(USER, user), eq(PROJECT, project)); + } + private Document getUpdatedFields(Image image) { return new Document(STATUS, image.getStatus().toString()) .append(IMAGE_FULL_NAME, image.getFullName()) diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/ImageExploratoryResource.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/ImageExploratoryResource.java index 95dcdb469..8c5c76861 100644 --- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/ImageExploratoryResource.java +++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/ImageExploratoryResource.java @@ -22,7 +22,9 @@ package com.epam.datalab.backendapi.resources; import com.epam.datalab.auth.UserInfo; import com.epam.datalab.backendapi.domain.RequestId; import com.epam.datalab.backendapi.resources.dto.ExploratoryImageCreateFormDTO; +import com.epam.datalab.backendapi.resources.dto.ImageFilter; import com.epam.datalab.backendapi.resources.dto.ImageInfoRecord; +import com.epam.datalab.backendapi.resources.dto.ProjectImagesInfo; import com.epam.datalab.backendapi.service.ImageExploratoryService; import com.google.inject.Inject; import io.dropwizard.auth.Auth; @@ -98,6 +100,24 @@ public class ImageExploratoryResource { return Response.ok(images).build(); } + + @GET + @Path("user") + public Response getImagesForUser(@Auth UserInfo ui) { + log.debug("Getting images for user {}", ui.getName()); + final List<ProjectImagesInfo> images = imageExploratoryService.getImagesOfUser(ui); + return Response.ok(images).build(); + } + + @POST + @Path("user") + public Response getImagesForUser(@Auth UserInfo ui, @Valid @NotNull ImageFilter imageFilter) { + log.debug("Getting images for user {} with filter {}", ui.getName(), imageFilter); + final List<ProjectImagesInfo> images = imageExploratoryService.getImagesOfUserWithFilter(ui ,imageFilter); + return Response.ok(images).build(); + } + + @GET @Path("{name}") public Response getImage(@Auth UserInfo ui, diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageFilter.java similarity index 58% copy from services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java copy to services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageFilter.java index 18692c34c..09c50ef58 100644 --- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java +++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageFilter.java @@ -19,19 +19,39 @@ package com.epam.datalab.backendapi.resources.dto; +import com.epam.datalab.cloud.CloudProvider; import com.epam.datalab.dto.exploratory.ImageStatus; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +import java.util.List; @Data +@NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class ImageInfoRecord { - private final String name; - private final String description; - private final String project; - private final String endpoint; - private final String user; - private final String application; - private final String fullName; - private final ImageStatus status; +public class ImageFilter { + @NonNull + private String imageName; + @NonNull + @JsonProperty("date_start") + private String dateStart; + @NonNull + @JsonProperty("date_end") + private String dateEnd; + @NonNull + private List<CloudProvider> cloudProviders; + @NonNull + private List<ImageStatus> statuses; +// @NonNull +// private List<> sharingStatuses; + @NonNull + private List<String> templateNames; + @NonNull + private List<String> instanceNames; + @NonNull + private List<String> projects; + } diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java index 18692c34c..00e5c94c6 100644 --- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java +++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java @@ -19,19 +19,26 @@ package com.epam.datalab.backendapi.resources.dto; +import com.epam.datalab.cloud.CloudProvider; import com.epam.datalab.dto.exploratory.ImageStatus; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; +import java.time.LocalDateTime; + @Data @JsonIgnoreProperties(ignoreUnknown = true) public class ImageInfoRecord { private final String name; + private final String creationDate; private final String description; private final String project; private final String endpoint; private final String user; private final String application; + private final String instanceName; + private final CloudProvider cloudProvider; private final String fullName; private final ImageStatus status; + private final String sharedStatus; } diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectImagesInfo.java similarity index 64% copy from services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts copy to services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectImagesInfo.java index 38df6d2c5..06da51f28 100644 --- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts +++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ProjectImagesInfo.java @@ -17,19 +17,23 @@ * under the License. */ -export const sideBarNamesConfig: Record<string, string> = { - resourses: 'Resources', - reports: 'Reports', - audit: 'Audit', - billing: 'Billing', - administration: 'Administration', - users: 'Users', - projects: 'Projects', - resources: 'Resources', - configuration: 'Configuration' -} +package com.epam.datalab.backendapi.resources.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; -export interface UserInfo { - email: string; - name: string; +@AllArgsConstructor +@Builder +@EqualsAndHashCode +@ToString +public class ProjectImagesInfo { + @JsonProperty + private String project; + @JsonProperty + private List<ImageInfoRecord> images; } diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/ImageExploratoryService.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/ImageExploratoryService.java index fae72a33f..99973f1a6 100644 --- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/ImageExploratoryService.java +++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/ImageExploratoryService.java @@ -20,7 +20,9 @@ package com.epam.datalab.backendapi.service; import com.epam.datalab.auth.UserInfo; +import com.epam.datalab.backendapi.resources.dto.ImageFilter; import com.epam.datalab.backendapi.resources.dto.ImageInfoRecord; +import com.epam.datalab.backendapi.resources.dto.ProjectImagesInfo; import com.epam.datalab.model.exploratory.Image; import java.util.List; @@ -36,4 +38,7 @@ public interface ImageExploratoryService { ImageInfoRecord getImage(String user, String name, String project, String endpoint); List<ImageInfoRecord> getImagesForProject(String project); + + List<ProjectImagesInfo> getImagesOfUser(UserInfo user); + List<ProjectImagesInfo> getImagesOfUserWithFilter(UserInfo user, ImageFilter imageFilter); } diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImpl.java b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImpl.java index 8c6802167..c0cd6e54d 100644 --- a/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImpl.java +++ b/services/self-service/src/main/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImpl.java @@ -30,7 +30,9 @@ import com.epam.datalab.backendapi.dao.ExploratoryLibDAO; import com.epam.datalab.backendapi.dao.ImageExploratoryDAO; import com.epam.datalab.backendapi.domain.EndpointDTO; import com.epam.datalab.backendapi.domain.ProjectDTO; +import com.epam.datalab.backendapi.resources.dto.ImageFilter; import com.epam.datalab.backendapi.resources.dto.ImageInfoRecord; +import com.epam.datalab.backendapi.resources.dto.ProjectImagesInfo; import com.epam.datalab.backendapi.service.EndpointService; import com.epam.datalab.backendapi.service.ImageExploratoryService; import com.epam.datalab.backendapi.service.ProjectService; @@ -53,6 +55,7 @@ import com.google.inject.Singleton; import com.google.inject.name.Named; import lombok.extern.slf4j.Slf4j; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -104,6 +107,8 @@ public class ImageExploratoryServiceImpl implements ImageExploratoryService { .computationalLibraries(fetchComputationalLibs(libraries)) .dockerImage(userInstance.getImageName()) .exploratoryId(userInstance.getId()) + .instanceName(userInstance.getExploratoryName()) + .cloudProvider(userInstance.getCloudProvider()) .project(userInstance.getProject()) .endpoint(userInstance.getEndpoint()) .build()); @@ -154,6 +159,36 @@ public class ImageExploratoryServiceImpl implements ImageExploratoryService { return imageExploratoryDao.getImagesForProject(project); } + @Override + public List<ProjectImagesInfo> getImagesOfUser(UserInfo user) { + log.debug("Loading list of images for user {}", user.getName()); + return projectService.getUserProjects(user, Boolean.FALSE) + .stream() + .map( p-> { + List<ImageInfoRecord> images = imageExploratoryDao.getImagesOfUser(user.getName(), p.getName()); + return ProjectImagesInfo.builder() + .project(p.getName()) + .images(images) + .build(); + }) + .collect(Collectors.toList()); + } + + @Override + public List<ProjectImagesInfo> getImagesOfUserWithFilter(UserInfo user, ImageFilter imageFilter) { + log.debug("Loading list of images for user {}", user.getName()); + return projectService.getUserProjects(user, Boolean.FALSE) + .stream() + .map( p-> { + List<ImageInfoRecord> images = imageExploratoryDao.getImagesOfUser(user.getName(), p.getName()); + return ProjectImagesInfo.builder() + .project(p.getName()) + .images(images) + .build(); + }) + .collect(Collectors.toList()); + } + private Map<String, List<Library>> fetchComputationalLibs(List<Library> libraries) { return libraries.stream() .filter(resourceTypePredicate(ResourceType.COMPUTATIONAL)) diff --git a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts index d166b7d40..af905a56e 100644 --- a/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts +++ b/services/self-service/src/main/resources/webapp/src/app/app.routing.module.ts @@ -37,12 +37,13 @@ import {ProjectAdminGuard} from './core/services/projectAdmin.guard'; import {ReportingComponent} from './reports/reporting/reporting.component'; import {OdahuComponent} from './administration/odahu/odahu.component'; import {AuditComponent} from './reports/audit/audit.component'; +import {ImagesComponent} from './resources/images/images.component'; const routes: Routes = [ { path: 'login', component: LoginComponent - }, + }, { path: '', canActivate: [CheckParamsGuard], @@ -50,19 +51,24 @@ const routes: Routes = [ children: [ { path: '', - redirectTo: 'resources_list', + redirectTo: 'instances', pathMatch: 'full' - }, + }, { - path: 'resources_list', + path: 'instances', component: ResourcesComponent, canActivate: [AuthorizationGuard] - }, + }, + { + path: 'images', + component: ImagesComponent, + canActivate: [AuthorizationGuard] + }, { path: 'billing_report', component: ReportingComponent, canActivate: [AuthorizationGuard, CloudProviderGuard] - }, + }, { path: 'projects', component: ProjectComponent, @@ -76,12 +82,12 @@ const routes: Routes = [ path: 'roles', component: RolesComponent, canActivate: [AuthorizationGuard, AdminGuard], - }, + }, { path: 'environment_management', component: ManagementComponent, canActivate: [AuthorizationGuard, AdminGuard] - }, + }, { path: 'configuration', component: ConfigurationComponent, @@ -91,12 +97,12 @@ const routes: Routes = [ path: 'swagger', component: SwaggerComponent, canActivate: [AuthorizationGuard] - }, + }, { path: 'help/publickeyguide', component: PublicKeyGuideComponent, canActivate: [AuthorizationGuard] - }, + }, { path: 'help/accessnotebookguide', component: AccessNotebookGuideComponent, @@ -108,16 +114,16 @@ const routes: Routes = [ canActivate: [AuthorizationGuard, AuditGuard], }, ] - }, + }, { path: 'terminal/:id/:endpoint', component: WebterminalComponent - }, + }, { path: '403', component: AccessDeniedComponent, canActivate: [AuthorizationGuard] - }, + }, { path: '**', component: NotFoundComponent diff --git a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts index 20a45126c..33dc379fd 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/core.module.ts @@ -48,9 +48,9 @@ import { NoCacheInterceptor } from './interceptors/nocache.interceptor'; import { ErrorInterceptor } from './interceptors/error.interceptor'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import {ConfigurationService} from './services/configutration.service'; -import {AuditGuard, OdahuDeploymentService} from './services'; -import {ProjectAdminGuard} from './services/projectAdmin.guard'; +import { ConfigurationService } from './services/configutration.service'; +import { AuditGuard, OdahuDeploymentService, UserImagesPageService } from './services'; +import { ProjectAdminGuard } from './services/projectAdmin.guard'; @NgModule({ imports: [CommonModule], @@ -90,6 +90,7 @@ export class CoreModule { UserAccessKeyService, ConfigurationService, OdahuDeploymentService, + UserImagesPageService, { provide: MatDialogRef, useValue: {} }, { provide: MAT_DIALOG_DATA, useValue: [] }, diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts similarity index 68% copy from services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts copy to services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts index 38df6d2c5..df57fee83 100644 --- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/capitalize-first-letter.pipe.ts @@ -17,19 +17,17 @@ * under the License. */ -export const sideBarNamesConfig: Record<string, string> = { - resourses: 'Resources', - reports: 'Reports', - audit: 'Audit', - billing: 'Billing', - administration: 'Administration', - users: 'Users', - projects: 'Projects', - resources: 'Resources', - configuration: 'Configuration' -} -export interface UserInfo { - email: string; - name: string; +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'capitalizeFirstLetter' }) + +export class CapitalizeFirstLetterPipe implements PipeTransform { + transform(value: string): string { + if (!value) { + return ''; + } + const firstLetter = value. substring(0, 1). toUpperCase(); + return `${firstLetter}${value.substring(1).toLowerCase()}`; + } } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts similarity index 70% copy from services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts copy to services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts index 399ce1d31..d333ad504 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/capitalize-first-letter-pipe/index.ts @@ -17,9 +17,14 @@ * under the License. */ -export * from './keys-pipe'; -export * from './underscoreless-pipe'; -export * from './lib-sort-pipe'; -export * from './replace-breaks-pipe'; -export * from './highlight.pipe'; -export * from './convert-action-pipe'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CapitalizeFirstLetterPipe } from './capitalize-first-letter.pipe'; + +@NgModule({ + imports: [CommonModule], + declarations: [CapitalizeFirstLetterPipe], + exports: [CapitalizeFirstLetterPipe] +}) + +export class CapitalizeFirstLetterPipeModule { } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts index 399ce1d31..bc2e2c7b6 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/pipes/index.ts @@ -23,3 +23,4 @@ export * from './lib-sort-pipe'; export * from './replace-breaks-pipe'; export * from './highlight.pipe'; export * from './convert-action-pipe'; +export * from './capitalize-first-letter-pipe'; diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts index 1a8075973..3dd07cbd7 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts @@ -34,7 +34,7 @@ export class AppRoutingService { } redirectToHomePage(): void { - this.router.navigate(['/resources_list']); + this.router.navigate(['/instances']); } redirectToHealthStatusPage(): void { diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts index c4fb49bd7..cd39b0e67 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts @@ -17,13 +17,13 @@ * under the License. */ -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {HttpClient} from '@angular/common/http'; -import { Dictionary } from '../collections'; -import { environment } from '../../../environments/environment'; -import { HTTPMethod } from '../util'; +import {Dictionary} from '../collections'; +import {environment} from '../../../environments/environment'; +import {HTTPMethod} from '../util'; // we can now access environment.apiUrl const API_URL = environment.apiUrl; @@ -81,6 +81,7 @@ export class ApplicationServiceFacade { private static readonly AUDIT = 'audit'; private static readonly CONFIG = 'config'; private static readonly QUOTA = 'quota'; + private static readonly IMAGE_PAGE = 'image_page'; private requestRegistry: Dictionary<string>; @@ -180,6 +181,12 @@ export class ApplicationServiceFacade { null); } + buildGetUserImagePage(): Observable<any> { + return this.buildRequest(HTTPMethod.GET, + this.requestRegistry.Item(ApplicationServiceFacade.IMAGE_PAGE), + null); + } + public buildGetTemplatesRequest(params): Observable<any> { return this.buildRequest(HTTPMethod.GET, this.requestRegistry.Item(ApplicationServiceFacade.TEMPLATES) + params, @@ -714,6 +721,8 @@ export class ApplicationServiceFacade { // Exploratory Environment this.requestRegistry.Add(ApplicationServiceFacade.PROVISIONED_RESOURCES, '/api/infrastructure/info'); + this.requestRegistry.Add(ApplicationServiceFacade.IMAGE_PAGE, + '/api/infrastructure_provision/exploratory_environment/image/user'); this.requestRegistry.Add(ApplicationServiceFacade.EXPLORATORY_ENVIRONMENT, '/api/infrastructure_provision/exploratory_environment'); this.requestRegistry.Add(ApplicationServiceFacade.TEMPLATES, diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts index d84741540..68ce7e151 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/index.ts @@ -40,3 +40,4 @@ export * from './storage.service'; export * from './project.service'; export * from './odahu-deployment.service'; export * from './endpoint.service'; +export * from './user-images-page.service'; diff --git a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts similarity index 56% copy from services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java copy to services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts index 18692c34c..05e9e9077 100644 --- a/services/self-service/src/main/java/com/epam/datalab/backendapi/resources/dto/ImageInfoRecord.java +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts @@ -17,21 +17,23 @@ * under the License. */ -package com.epam.datalab.backendapi.resources.dto; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { ErrorUtils } from '../util'; -import com.epam.datalab.dto.exploratory.ImageStatus; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; +import { ApplicationServiceFacade } from './applicationServiceFacade.service'; +import { ProjectModel } from '../../resources/images'; -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class ImageInfoRecord { - private final String name; - private final String description; - private final String project; - private final String endpoint; - private final String user; - private final String application; - private final String fullName; - private final ImageStatus status; +@Injectable() +export class UserImagesPageService { + constructor(private applicationServiceFacade: ApplicationServiceFacade) { } + + + getUserImagePageInfo(): Observable<ProjectModel[]> { + return this.applicationServiceFacade.buildGetUserImagePage() + .pipe( + catchError(ErrorUtils.handleServiceError) + ); + } } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts index 3f2d5f8d6..05061c533 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts @@ -23,6 +23,7 @@ import { catchError, map } from 'rxjs/operators'; import { ErrorUtils } from '../util/'; import { ApplicationServiceFacade } from './applicationServiceFacade.service'; +import {ProjectModel} from '../../resources/images/images.model'; @Injectable() export class UserResourceService { @@ -109,9 +110,9 @@ export class UserResourceService { } public suspendComputationalResource( - projectName: string, - notebookName: string, - computationalResourceName: string, + projectName: string, + notebookName: string, + computationalResourceName: string, provider: string ): Observable<{}> { const body = JSON.stringify('/' + projectName + '/' + notebookName + '/' + computationalResourceName + '/terminate'); @@ -123,10 +124,10 @@ export class UserResourceService { } public toggleStopStartAction( - project: string, - notebook: string, - resource: string, - action, + project: string, + notebook: string, + resource: string, + action, provider: string ): Observable<{}> { const url = `/${project}/${notebook}/${resource}/${action}`; diff --git a/services/self-service/src/main/resources/webapp/src/app/reports/reporting/reporting-grid/reporting-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/reports/reporting/reporting-grid/reporting-grid.component.ts index 513ac45c1..9e45d9133 100644 --- a/services/self-service/src/main/resources/webapp/src/app/reports/reporting/reporting-grid/reporting-grid.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/reports/reporting/reporting-grid/reporting-grid.component.ts @@ -62,13 +62,13 @@ export class ReportingGridComponent implements OnInit { isFiltered: boolean = false; active: object = {}; displayedColumns: string[] = [ - 'name', 'user', 'project', - 'type', 'status', 'shape', + 'name', 'user', 'project', + 'type', 'status', 'shape', 'service', 'empty', 'charge' ]; displayedFilterColumns: string[] = [ - 'name-filter', 'user-filter', 'project-filter', - 'type-filter', 'status-filter', 'shape-filter', + 'name-filter', 'user-filter', 'project-filter', + 'type-filter', 'status-filter', 'shape-filter', 'service-filter', 'empty-filter', 'actions' ]; filtered: any; @@ -106,7 +106,7 @@ export class ReportingGridComponent implements OnInit { ngOnInit() { this.userAgentIndex = window.navigator.userAgent.indexOf('Firefox'); - + window.setTimeout(() => { this.isScrollButtonsVisible = this.tableWrapper.nativeElement.offsetWidth - this.table._elementRef.nativeElement.offsetWidth < 0; this.checkMaxRight(); @@ -128,7 +128,6 @@ export class ReportingGridComponent implements OnInit { refreshData(fullReport, report) { this.reportData = [...report]; - console.log(fullReport); this.fullReport = fullReport; this.checkFilters(); } @@ -168,7 +167,7 @@ export class ReportingGridComponent implements OnInit { return 0; }); } - + this.refreshData(this.fullReport, report); this.removeSorting(); this.active[sortItem + direction] = true; @@ -223,7 +222,7 @@ export class ReportingGridComponent implements OnInit { const arg = this.tableWrapper.nativeElement.offsetWidth + this.tableWrapper.nativeElement.scrollLeft + 2 <= this.table._elementRef.nativeElement.offsetWidth; return this.isMaxRight.next(arg); - + } public onFilterNameUpdate(targetElement: any) { diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts index 010e5d22e..05f822f4f 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts @@ -127,9 +127,8 @@ export class BucketBrowserComponent implements OnInit, OnDestroy { } public toggleSelectedFile(file, type): void { - console.log(file, type); - type === 'file' - ? file.isSelected = !file.isSelected + type === 'file' + ? file.isSelected = !file.isSelected : file.isFolderSelected = !file.isFolderSelected; this.selected = this.folderItems.filter(item => item.isSelected); this.selectedFolderForAction = this.folderItems.filter(item => item.isFolderSelected); @@ -260,8 +259,8 @@ export class BucketBrowserComponent implements OnInit, OnDestroy { this.objectPath = event.pathObject; this.path = event.path; this.originFolderItems = this.folderItems.map(v => v); - this.pathInsideBucket = this.path.indexOf('/') !== -1 - ? this.path.slice(this.path.indexOf('/') + 1) + '/' + this.pathInsideBucket = this.path.indexOf('/') !== -1 + ? this.path.slice(this.path.indexOf('/') + 1) + '/' : ''; this.folderItems.forEach(item => item.isSelected = false); } @@ -327,7 +326,7 @@ export class BucketBrowserComponent implements OnInit, OnDestroy { if (!file) { file = waitUploading[0]; } - + file.status = 'uploading'; this.isFileUploading = this.addedFiles.some(v => v.status === 'uploading'); this.isQueueFull = this.addedFiles.some(v => v.status === 'waiting'); @@ -350,7 +349,7 @@ export class BucketBrowserComponent implements OnInit, OnDestroy { this.sendFile(this.addedFiles.find(v => v.status === 'waiting')); this.bucketDataService.refreshBucketdata(this.bucketName, this.endpoint); } - }, + }, error => { window.clearInterval(file.interval); file.status = 'failed'; @@ -412,7 +411,7 @@ export class BucketBrowserComponent implements OnInit, OnDestroy { selected[0].progress = 0; }, 1000); } - }, + }, error => { this.toastr.error(error.message || 'File downloading error!', 'Oops!'); selected[0]['isDownloading'] = false; @@ -461,7 +460,7 @@ export class BucketBrowserComponent implements OnInit, OnDestroy { public copyPath(): void { const selected = this.folderItems.filter(item => item.isSelected || item.isFolderSelected)[0]; const pathToItem = `${this.pathInsideBucket}${selected.item}${selected.isFolderSelected ? '/' : ''}`; - + const cloud = this.getCloud(); const protocol = HelpUtils.getBucketProtocol(cloud); if (cloud !== 'azure') { diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html new file mode 100644 index 000000000..595cd2893 --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.html @@ -0,0 +1,240 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> + +<section class="image-list--wrapper"> + <nav class="image-list__nav-bar"> + <div class="selection"> + <div class="mat-reset"> + <div class="control selector-wrapper" + [ngClass]="{'disabled-select': !isProjectsMoreThanOne}" + > + <mat-form-field> + <mat-label>Select project</mat-label> + + <mat-select + disableOptionCentering + panelClass="top-select scrolling" + [disabled]="!projectList.length" + > + <mat-option + *ngIf="isProjectsMoreThanOne" + (click)="onSelectClick('')" + > + Show all + </mat-option> + <mat-option + *ngFor="let project of projectList" + [value]="project" + (click)="onSelectClick(project)" + > + {{ project }} + </mat-option> + <mat-option *ngIf="!projectList?.length" class="multiple-select ml-10" disabled> + Projects list is empty + </mat-option> + </mat-select> + <button class="caret" [disabled]="false"> + <i class="material-icons">keyboard_arrow_down</i> + </button> + </mat-form-field> + </div> + </div> + </div> + + <div class="button--wrapper"> + <span class="action-button--wrapper"> + <button + type="button" + class="butt action-button" + mat-raised-button + [disabled]="true" + (click)="onActionClick()" + > + Actions + <i class="material-icons" >{{ !isActionsOpen ? 'expand_more' : 'expand_less' }}</i> + </button> + </span> + <span> + <button mat-raised-button class="butt"> + <i class="material-icons highlight">autorenew</i> + Refresh + </button> + </span> + </div> + </nav> + <mat-divider></mat-divider> + + <table mat-table [dataSource]="dataSource" class="mat-elevation-z8 demo-table data-grid"> + <!-- Position Column --> + <ng-container matColumnDef="checkbox"> + <th mat-header-cell *matHeaderCellDef class="image-checkbox--wrapper"> + <div class="header-cell--wrapper"> + <span> + <datalab-checkbox + (click)="allCheckboxToggle()" + [checked]="checkboxSelected" + class="image-checkbox" + ></datalab-checkbox> + </span> + <i class="material-icons header-cell__dots"> + <span>more_vert</span> + </i> + </div> + </th> + <td mat-cell *matCellDef="let element" class="image-checkbox--wrapper"> + <datalab-checkbox + (click)="onCheckboxClick(element)" + class="image-checkbox" + [checked]="element.isSelected" + ></datalab-checkbox> + </td> + </ng-container> + + <ng-container matColumnDef="imageName"> + <th mat-header-cell *matHeaderCellDef> + <div class="header-cell--wrapper"> + <span>{{tableHeaderCellTitles.imageName}}</span> + <i class="material-icons header-cell__dots"> + <span>more_vert</span> + </i> + </div> + </th> + <td mat-cell *matCellDef="let element"> {{element.name}} </td> + </ng-container> + + <ng-container matColumnDef="creationDate"> + <th mat-header-cell *matHeaderCellDef> + <div class="header-cell--wrapper"> + <span>{{tableHeaderCellTitles.creationDate}}</span> + <i class="material-icons header-cell__dots"> + <span>more_vert</span> + </i> + </div> + </th> + <td mat-cell *matCellDef="let element"> + <span class="date-item"> {{element.creationDate | date: 'yyyy-MM-dd'}} </span> + <span> {{element.creationDate | date: 'HH:mm:ss'}} </span> + </td> + </ng-container> + + <ng-container matColumnDef="provider"> + <th mat-header-cell *matHeaderCellDef> + <div class="header-cell--wrapper"> + <span>{{tableHeaderCellTitles.provider}}</span> + <i class="material-icons header-cell__dots"> + <span>more_vert</span> + </i> + </div> + </th> + <td mat-cell *matCellDef="let element"> {{element.cloudProvider}} </td> + </ng-container> + + <ng-container matColumnDef="imageStatus"> + <th mat-header-cell *matHeaderCellDef> + <div class="header-cell--wrapper"> + <span>{{tableHeaderCellTitles.imageStatus}}</span> + <i class="material-icons header-cell__dots"> + <span>more_vert</span> + </i> + </div> + </th> + <td mat-cell *matCellDef="let element" ngClass="{{ element.status.toLowerCase() || ''}}"> + {{element.status | capitalizeFirstLetter}} + </td> + </ng-container> + + <ng-container matColumnDef="sharedStatus"> + <th mat-header-cell *matHeaderCellDef> + <div class="header-cell--wrapper"> + <span>{{tableHeaderCellTitles.sharedStatus}}</span> + <i class="material-icons header-cell__dots"> + <span>more_vert</span> + </i> + </div> + </th> + <td mat-cell *matCellDef="let element"> + <div class="shared-status--wrapper"> + <span class="shared-status"> {{element.shared}} </span> + <span class="currency_details" > + <i class="material-icons">help_outline</i> + </span> + </div> + </td> + </ng-container> + + <ng-container matColumnDef="templateName"> + <th mat-header-cell *matHeaderCellDef> + <div class="header-cell--wrapper"> + <span>{{tableHeaderCellTitles.templateName}}</span> + <i class="material-icons header-cell__dots"> + <span>more_vert</span> + </i> + </div> + </th> + <td mat-cell *matCellDef="let element"> {{element.application}} </td> + </ng-container> + + <ng-container matColumnDef="instanceName"> + <th mat-header-cell *matHeaderCellDef> + <div class="header-cell--wrapper"> + <span>{{tableHeaderCellTitles.instanceName}}</span> + <i class="material-icons header-cell__dots"> + <span>more_vert</span> + </i> + </div> + </th> + <td mat-cell *matCellDef="let element"> {{element.instanceName}} </td> + </ng-container> + + <ng-container matColumnDef="actions"> + <th mat-header-cell *matHeaderCellDef> {{tableHeaderCellTitles.actions}} </th> + <td mat-cell *matCellDef="let element" class="settings actions-col"> + + <div class="button--wrapper"> + <span class="currency_details" > + <i class="material-icons">help_outline</i> + </span> + <span #settings class="actions" (click)="actions.toggle($event, settings)"></span> + </div> + <bubble-up #actions class="list-menu" position="bottom-left" alternative="top-left"> + <ul class="list-unstyled"> + <li + matTooltip="Unable to terminate notebook because at least one compute is in progress" + matTooltipPosition="above" + > + <div> + <i class="material-icons">phonelink_off</i> + <span>Terminate</span> + </div> + </li> + <li> + <div> + <i class="material-icons">create</i> + <span>Share</span> + </div> + </li> + </ul> + </bubble-up> + </td> + </ng-container> + + <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> + <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> + </table> +</section> diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss new file mode 100644 index 000000000..e203266ef --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.scss @@ -0,0 +1,102 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.image-list { + &--wrapper { + padding: 12px 15px; + } + + &__nav-bar { + display: flex; + justify-content: space-between; + } +} + +.action-button { + padding: 0 38px 0 45px; + + &--wrapper { + margin-right: 10px; + } +} + +.demo-table { + width: 100%; +} + +.mat-column-demo-position { + width: 32px; + border-right: 1px solid currentColor; + padding-right: 24px; + text-align: center; +} + +.mat-column-demo-name { + padding-left: 16px; + font-size: 20px; +} + +.mat-column-demo-weight { + font-style: italic; +} + +.mat-column-demo-symbol { + width: 32px; + text-align: center; + font-weight: bold; +} + +.image-checkbox { + &--wrapper { + padding-left: 10px !important; + } +} + +.currency_details { + display: flex; + color: #35afd5; + cursor: pointer; + transition: all 0.45s ease-in-out; +} + +.material-icons { + font-size: 18px; +} + +.button--wrapper, +.header-cell--wrapper { + display: flex; + justify-content: space-between; +} + +.shared-status { + padding-right: 16px; + + &--wrapper { + display: flex; + } +} + +.header-cell__dots { + margin-right: 10px; +} + +.date-item { + margin-right: 10px; +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts new file mode 100644 index 000000000..cd39142fa --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Component, OnInit } from '@angular/core'; + +import { ToastrService } from 'ngx-toastr'; + +import { GeneralEnvironmentStatus } from '../../administration/management/management.model'; +import { HealthStatusService, UserImagesPageService } from '../../core/services'; +import { ImageModel, ProjectModel } from './images.model'; +import { Image_Table_Column_Headers } from './images.config'; + + +interface Aaa { + isSelected?: boolean; + imageName: string; + creationDate: string; + provider: string; + imageStatus: string; + sharedStatus: 'Private' | 'Shared'; + templateName: string; + instanceName: string; + actions: object; +} + + +const tableTitles = <const>['checkbox', 'imageName', 'creationDate', 'provider', 'imageStatus', 'sharedStatus', 'templateName', 'instanceName', 'actions']; + +@Component({ + selector: 'datalab-images', + templateUrl: './images.component.html', + styleUrls: [ + './images.component.scss', + '../resources-grid/resources-grid.component.scss', + '../resources.component.scss' + ] +}) + +export class ImagesComponent implements OnInit { + isActionsOpen: boolean = false; + healthStatus: GeneralEnvironmentStatus; + tableHeaderCellTitles: typeof Image_Table_Column_Headers = Image_Table_Column_Headers; + displayedColumns: typeof tableTitles = tableTitles; + dataSource: ImageModel[] = []; + checkboxSelected: boolean = false; + projectList: string[] = []; + private cashedImageListData: ProjectModel[] = []; + + constructor( + private healthStatusService: HealthStatusService, + public toastr: ToastrService, + private userImagesPageService: UserImagesPageService + ) { } + + ngOnInit(): void { + this.getEnvironmentHealthStatus(); + this.getUserImagePageInfo(); + } + + onCheckboxClick(element: ImageModel) { + element.isSelected = !element.isSelected; + } + + allCheckboxToggle(): void { + this.checkboxSelected = !this.checkboxSelected; + + if (this.checkboxSelected) { + this.dataSource.forEach(image => image.isSelected = true); + } else { + this.dataSource.forEach(image => image.isSelected = false); + } + } + + onActionClick(): void { + this.isActionsOpen = !this.isActionsOpen; + } + + onSelectClick(projectName: string): void { + if (!projectName) { + this.dataSource = this.getImageList(); + } + const { images } = this.cashedImageListData.find(({project}) => project === projectName); + this.dataSource = [...images]; + } + + private getImageList() { + return this.cashedImageListData.reduce((acc, {images}) => [...acc, ...images], []); + } + + private getEnvironmentHealthStatus() { + this.healthStatusService.getEnvironmentHealthStatus().subscribe( + (result: GeneralEnvironmentStatus) => { + this.healthStatus = result; + }, + error => this.toastr.error(error.message, 'Oops!') + ); + } + + private getUserImagePageInfo(): void { + this.userImagesPageService.getUserImagePageInfo().subscribe(imageListData => this.initImageTable(imageListData)); + } + + private initImageTable(imagePageList: ProjectModel[]) { + this.cashedImageListData = imagePageList; + this.getProjectList(imagePageList); + this.dataSource = this.getImageList(); + } + + private getProjectList(imagePageList: ProjectModel[]): void { + if (!imagePageList) { + return; + } + imagePageList.forEach(({project}) => this.projectList.push(project)); + } + + get isProjectsMoreThanOne () { + return this.projectList.length > 1; + } +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts new file mode 100644 index 000000000..f2ac9f810 --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts @@ -0,0 +1,10 @@ +export enum Image_Table_Column_Headers { + imageName = 'Image name', + creationDate = 'Creation date', + provider = 'Provider', + imageStatus = 'Image status', + sharedStatus = 'Shared status', + templateName = 'Template name', + instanceName = 'Instance name', + actions = 'Actions', +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts new file mode 100644 index 000000000..b5b725a96 --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts @@ -0,0 +1,20 @@ +export interface ProjectModel { + project: string; + images: ImageModel[]; +} + +export interface ImageModel { + application: string; + cloudProvider: 'AWS' | 'GCP' | 'Azure'; + creationDate: string; + description: string; + endpoint: string; + fullName: string; + instanceName: string; + name: string; + project: string; + shared: 'private' | 'shared'; + status: 'created' | 'creating' | 'terminated' | 'terminating' | 'failed'; + user: string; + isSelected?: boolean; +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/index.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/index.ts new file mode 100644 index 000000000..365e9989d --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/index.ts @@ -0,0 +1,2 @@ +export * from './images.config'; +export * from './images.model'; diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts index 55c824d95..28b79ecd8 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts @@ -32,14 +32,13 @@ import { ProjectService, UserResourceService, OdahuDeploymentService } from '../ import { ExploratoryModel } from './resources-grid.model'; import { FilterConfigurationModel } from './filter-configuration.model'; import { GeneralEnvironmentStatus } from '../../administration/management/management.model'; -import { ConfirmationDialogType } from '../../shared'; +import { ConfirmationDialogComponent, ConfirmationDialogType } from '../../shared'; import { SortUtils, CheckUtils } from '../../core/util'; import { DetailDialogComponent } from '../exploratory/detail-dialog'; import { AmiCreateDialogComponent } from '../exploratory/ami-create-dialog'; import { InstallLibrariesComponent } from '../exploratory/install-libraries'; import { ComputationalResourceCreateDialogComponent } from '../computational/computational-resource-create-dialog/computational-resource-create-dialog.component'; import { CostDetailsDialogComponent } from '../exploratory/cost-details-dialog'; -import { ConfirmationDialogComponent } from '../../shared/modal-dialog/confirmation-dialog'; import { SchedulerComponent } from '../scheduler'; import { DICTIONARY } from '../../../dictionary/global.dictionary'; import { ProgressBarService } from '../../core/services/progress-bar.service'; @@ -195,9 +194,9 @@ export class ResourcesGridComponent implements OnInit { this.buildGrid(); } - public containsNotebook(notebook_name: string, envoirmentNames: Array<string>): boolean { - if (notebook_name && envoirmentNames.length ) { - return envoirmentNames + public containsNotebook(notebook_name: string, environmentNames: Array<string>): boolean { + if (notebook_name && environmentNames.length ) { + return environmentNames .some(item => CheckUtils.delimitersFiltering(notebook_name) === CheckUtils.delimitersFiltering(item)); } return false; diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts index 8c8d8d7d8..64e5855a4 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.model.ts @@ -148,10 +148,3 @@ export class ExploratoryModel { } } } - -// export interface Exploratory { -// project: string; -// endpoints: []; -// projectEndpoints: []; -// exploratory: ExploratoryModel[]; -// } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html index 131aa638d..23117f7f2 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.html @@ -26,9 +26,9 @@ [matTooltipClass]="'full-size-tooltip'" [matTooltipDisabled]="healthStatus?.projectAssigned && resourcesGrid.activeProjectsList?.length !== 0" > - <button - mat-raised-button - class="butt butt-create" + <button + mat-raised-button + class="butt butt-create" (click)="createEnvironment()" [disabled]="!healthStatus?.projectAssigned || !resourcesGrid.activeProjectsList?.length" > @@ -36,26 +36,26 @@ </button> </span> <div class="mat-reset"> - <div class="control selector-wrapper" *ngIf="projects?.length" + <div class="control selector-wrapper" *ngIf="projects?.length" [ngClass]="{'disabled-select': !isProjectsMoreThanOne}" > <mat-form-field> <mat-label>Select project</mat-label> - <mat-select + <mat-select disableOptionCentering - [(value)]="resourcesGrid.activeProject" + [(value)]="resourcesGrid.activeProject" panelClass="top-select scrolling" [disabled]="!isProjectsMoreThanOne" > - <mat-option - *ngIf="projects?.length > 1" + <mat-option + *ngIf="projects?.length > 1" (click)="setActiveProject('')" > Show all </mat-option> - <mat-option - *ngFor="let project of projects" + <mat-option + *ngFor="let project of projects" [value]="project" (click)="setActiveProject(project)" > @@ -72,15 +72,15 @@ </div> <div> - <span + <span matTooltip="{{!this.bucketStatus?.view ? 'You have not permission to open bucket browser' : 'You have not any bucket'}}" matTooltipPosition="above" matTooltipDisabled="{{resourcesGrid.bucketsList?.length > 0 && this.bucketStatus?.view}}" [matTooltipClass]="'full-size-tooltip'" > - <button - mat-raised-button - class="butt butt-tool" + <button + mat-raised-button + class="butt butt-tool" (click)="bucketBrowser(this.bucketStatus?.view)" [disabled]="!this.bucketStatus?.view || resourcesGrid.bucketsList?.length === 0" > diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts index e876331bf..6b0a1b764 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.component.ts @@ -86,7 +86,7 @@ export class ResourcesComponent implements OnInit { bucketStatus: this.bucketStatus, buckets: this.resourcesGrid.bucketsList }, - panelClass: 'modal-fullscreen' + panelClass: 'modal-fullscreen' }) .afterClosed().subscribe(); } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts index 47aea4c70..996c347d4 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts @@ -31,6 +31,10 @@ import { MatTreeModule } from '@angular/material/tree'; import { BucketDataService } from './bucket-browser/bucket-data.service'; import { ConvertFileSizePipeModule } from '../core/pipes/convert-file-size'; import { BucketBrowserModule } from './bucket-browser/bucket-browser.module'; +import { ImagesComponent } from './images/images.component'; +import {CheckboxModule} from '../shared/checkbox'; +import {BubbleModule} from '../shared'; +import { CapitalizeFirstLetterPipeModule } from '../core/pipes'; @NgModule({ imports: [ @@ -42,12 +46,16 @@ import { BucketBrowserModule } from './bucket-browser/bucket-browser.module'; MaterialModule, MatTreeModule, ConvertFileSizePipeModule, - BucketBrowserModule + BucketBrowserModule, + CheckboxModule, + BubbleModule, + CapitalizeFirstLetterPipeModule ], declarations: [ ResourcesComponent, ManageUngitComponent, ConfirmDeleteAccountDialogComponent, + ImagesComponent, ], entryComponents: [ManageUngitComponent, ConfirmDeleteAccountDialogComponent], diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/bubble/bubble.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/bubble/bubble.component.ts index 02e8ddef8..86bcfaaeb 100644 --- a/services/self-service/src/main/resources/webapp/src/app/shared/bubble/bubble.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/shared/bubble/bubble.component.ts @@ -17,17 +17,17 @@ * under the License. */ -import { - Component, - Input, - Output, - EventEmitter, +import { + Component, + Input, + Output, + EventEmitter, HostBinding, - ChangeDetectorRef, - ElementRef, + ChangeDetectorRef, + ElementRef, OnDestroy, - ViewEncapsulation, - HostListener + ViewEncapsulation, + HostListener } from '@angular/core'; import { BubblesCollector, BubbleService } from './bubble.service'; @@ -101,11 +101,14 @@ export class BubbleComponent implements OnDestroy { this.changeDirection = !this.isInViewport(bubbleElem); let isBubbleOutOfWrapper; - - if(document.querySelector('.wrapper')) { - isBubbleOutOfWrapper = bubbleElem.getBoundingClientRect().bottom > document.querySelector('.wrapper').getBoundingClientRect().bottom; + + if (document.querySelector('.wrapper')) { + isBubbleOutOfWrapper = bubbleElem.getBoundingClientRect() + .bottom > document.querySelector('.wrapper') + .getBoundingClientRect() + .bottom; } - + (this.changeDirection || isBubbleOutOfWrapper) && this.bubbleService.updatePosition(element, bubbleElem, this.alternative); } diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html index 8c52c46f9..60e635d99 100644 --- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html @@ -37,11 +37,11 @@ <!-- <a *ngIf="healthStatus.status" [routerLink]="['/environment_management']" class="statusbar"> <span class="material-icons" ngClass="{{healthStatus.status || ''}}">radio_button_checked</span> </a> --> - - <a - *ngIf="metadata" - class="statusbar about-btn--wrapper" - #info + + <a + *ngIf="metadata" + class="statusbar about-btn--wrapper" + #info (click)="actions.toggle($event, info)"> <span class="about-btn">About</span> </a> @@ -72,7 +72,7 @@ <a class="help-link" href="https://github.com/apache/incubator-datalab/blob/master/USER_GUIDE.md" target="_blank">Help</a> </span> - <span + <span class="material-icons account-icon--nav-bar account-icon" #login (click)="loginInfo.toggle($event, login)"> account_circle </span> @@ -85,53 +85,70 @@ <span class="user-mail">{{userData.email}}</span> <button type="button" class="logout-btn" (click)="logout_btnClick()"> Log out from account - </button> + </button> </div> </bubble-up> </div> </div> <mat-sidenav-container class="example-container" autosize > - <mat-sidenav - #drawer - mode="side" - opened - role="navigation" - [style.width]="isExpanded ? '220px' : '60px'" - disableClose + <mat-sidenav + #drawer + mode="side" + opened + role="navigation" + [style.width]="isExpanded ? '220px' : '60px'" + disableClose *ngIf="healthStatus" > <mat-nav-list > <nav> <div> - <a - class="nav-item" - [routerLink]="['/resources_list']" - [routerLinkActive]="['active']" - [routerLinkActiveOptions]="{exact:true}" - > - <span *ngIf="isExpanded; else resources">{{sideBarNames.resourses}}</span> - <ng-template #resources><i class="material-icons">dashboard</i></ng-template> + <a class="nav-item has-children"> + <span *ngIf="isExpanded">{{sideBarNames.resources}}</span> + <a + class="sub-nav-item" + [style.margin-left.px]="isExpanded ? '30' : '0'" + [routerLink]="['/instances']" + [routerLinkActive]="['active']" + [routerLinkActiveOptions]="{exact:true}" + > + <span *ngIf="isExpanded; else instances">{{sideBarNames.instances}}</span> + <ng-template #instances><i class="material-icons">laptop</i></ng-template> + </a> + + <a + class="sub-nav-item" + [style.margin-left.px]="isExpanded ? '30' : '0'" + [routerLink]="['/images']" + [routerLinkActive]="['active']" + [routerLinkActiveOptions]="{exact:true}" + > + <span *ngIf="isExpanded; else images">{{sideBarNames.images}}</span> + <ng-template #images><i class="material-icons">photo</i></ng-template> + </a> + </a> + <a class="nav-item has-children" *ngIf="healthStatus?.billingEnabled || healthStatus?.auditEnabled"> <span *ngIf="isExpanded">{{sideBarNames.reports}}</span> - <a - *ngIf="healthStatus?.auditEnabled" - class="sub-nav-item" - [routerLink]="['/audit']" + <a + *ngIf="healthStatus?.auditEnabled" + class="sub-nav-item" + [routerLink]="['/audit']" [style.margin-left.px]="isExpanded ? '30' : '0'" - [routerLinkActive]="['active']" + [routerLinkActive]="['active']" [routerLinkActiveOptions]="{exact:true}" > <span *ngIf="isExpanded; else audit">{{sideBarNames.audit}}</span> <ng-template #audit><i class="material-icons">library_books</i></ng-template> </a> - <a - *ngIf="healthStatus?.billingEnabled" - class="sub-nav-item" + <a + *ngIf="healthStatus?.billingEnabled" + class="sub-nav-item" [routerLink]="['/billing_report']" - [routerLinkActive]="['active']" - [routerLinkActiveOptions]="{exact:true}" + [routerLinkActive]="['active']" + [routerLinkActiveOptions]="{exact:true}" [style.margin-left.px]="isExpanded ? '30' : '0'" > <span *ngIf="isExpanded; else billing">{{sideBarNames.billing}}</span> @@ -141,21 +158,21 @@ <a class="nav-item has-children" *ngIf="healthStatus?.admin || healthStatus?.projectAdmin"> <span *ngIf="isExpanded">{{sideBarNames.administration}}</span> - <a - class="sub-nav-item" - [style.margin-left.px]="isExpanded ? '30' : '0'" + <a + class="sub-nav-item" + [style.margin-left.px]="isExpanded ? '30' : '0'" [routerLink]="['/roles']" - [routerLinkActive]="['active']" + [routerLinkActive]="['active']" [routerLinkActiveOptions]="{exact:true}" > <span *ngIf="isExpanded; else roles">{{sideBarNames.users}}</span> <ng-template #roles><i class="material-icons">account_box</i></ng-template> </a> - <a - class="sub-nav-item" - [style.margin-left.px]="isExpanded ? '30' : '0'" + <a + class="sub-nav-item" + [style.margin-left.px]="isExpanded ? '30' : '0'" [routerLink]="['/projects']" - [routerLinkActive]="['active']" + [routerLinkActive]="['active']" [routerLinkActiveOptions]="{exact:true}" > <span *ngIf="isExpanded; else projects">{{sideBarNames.projects}}</span> @@ -166,21 +183,21 @@ <!-- <span *ngIf="isExpanded; else odahu">Odahu deployment</span>--> <!-- <ng-template #odahu><i class="material-icons">get_app</i></ng-template>--> <!-- </a>--> - <a - class="sub-nav-item" + <a + class="sub-nav-item" [style.margin-left.px]="isExpanded ? '30' : '0'" - [routerLink]="['/environment_management']" + [routerLink]="['/environment_management']" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{exact:true}" > <span *ngIf="isExpanded; else env">{{sideBarNames.resources}}</span> <ng-template #env><i class="material-icons">settings</i></ng-template> </a> - <a - *ngIf="healthStatus?.admin" - class="sub-nav-item" + <a + *ngIf="healthStatus?.admin" + class="sub-nav-item" [style.margin-left.px]="isExpanded ? '30' : '0'" - [routerLink]="['/configuration']" + [routerLink]="['/configuration']" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{exact:true}" > @@ -188,7 +205,7 @@ <ng-template #env><i class="material-icons">build_circle</i></ng-template> </a> </a> - + </div> <!-- <div>--> <!-- <a class="nav-item" [routerLink]="['/swagger']" [routerLinkActive]="['active']"--> diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts index c88094060..4c906b3ef 100644 --- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.ts @@ -42,7 +42,7 @@ import { } from '@angular/animations'; import {skip, take} from 'rxjs/operators'; import {ProgressBarService} from '../../core/services/progress-bar.service'; -import { sideBarNamesConfig, UserInfo } from './navbar.config'; +import {Sidebar_Names_Config, UserInfo} from './navbar.config'; interface Quota { projectQuotas: {}; @@ -93,7 +93,7 @@ export class NavbarComponent implements OnInit, OnDestroy { isExpanded: boolean = true; healthStatus: GeneralEnvironmentStatus; subscriptions: Subscription = new Subscription(); - sideBarNames!: Record<string, string>; + sideBarNames: typeof Sidebar_Names_Config = Sidebar_Names_Config; userData!: UserInfo; commitMaxLength: number = 22; @@ -109,7 +109,6 @@ export class NavbarComponent implements OnInit, OnDestroy { ) { } ngOnInit() { - this.sideBarNames = sideBarNamesConfig; this.applicationSecurityService.loggedInStatus.subscribe(response => { this.subscriptions.unsubscribe(); this.subscriptions.closed = false; diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts index 38df6d2c5..44102f602 100644 --- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts +++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.config.ts @@ -17,19 +17,20 @@ * under the License. */ -export const sideBarNamesConfig: Record<string, string> = { - resourses: 'Resources', - reports: 'Reports', - audit: 'Audit', - billing: 'Billing', - administration: 'Administration', - users: 'Users', - projects: 'Projects', - resources: 'Resources', - configuration: 'Configuration' +export enum Sidebar_Names_Config { + reports = 'Reports', + audit = 'Audit', + billing = 'Billing', + administration = 'Administration', + users = 'Users', + projects = 'Projects', + resources = 'Resources', + configuration = 'Configuration', + instances = 'Instances', + images = 'Images' } export interface UserInfo { - email: string; - name: string; + email: string; + name: string; } diff --git a/services/self-service/src/main/resources/webapp/src/styles.scss b/services/self-service/src/main/resources/webapp/src/styles.scss index 00a27bbe2..5bc005390 100644 --- a/services/self-service/src/main/resources/webapp/src/styles.scss +++ b/services/self-service/src/main/resources/webapp/src/styles.scss @@ -121,6 +121,7 @@ mat-chip.mat-chip strong { text-align: left; } +.created, .running, .starting, .installed, diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/resources/ImageExploratoryResourceTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/resources/ImageExploratoryResourceTest.java index f62b1e3c6..6f7a0e224 100644 --- a/services/self-service/src/test/java/com/epam/datalab/backendapi/resources/ImageExploratoryResourceTest.java +++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/resources/ImageExploratoryResourceTest.java @@ -24,6 +24,7 @@ import com.epam.datalab.backendapi.domain.RequestId; import com.epam.datalab.backendapi.resources.dto.ExploratoryImageCreateFormDTO; import com.epam.datalab.backendapi.resources.dto.ImageInfoRecord; import com.epam.datalab.backendapi.service.ImageExploratoryService; +import com.epam.datalab.cloud.CloudProvider; import com.epam.datalab.dto.exploratory.ImageStatus; import com.epam.datalab.exceptions.ResourceAlreadyExistException; import com.epam.datalab.exceptions.ResourceNotFoundException; @@ -39,6 +40,7 @@ import javax.ws.rs.core.GenericType; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.time.LocalDateTime; import java.util.Collections; import java.util.List; @@ -275,8 +277,18 @@ public class ImageExploratoryResourceTest extends TestBase { } private List<ImageInfoRecord> getImageList() { - ImageInfoRecord imageInfoRecord = new ImageInfoRecord("someName", "someDescription", "someProject", "someEndpoint", "someUser", "someApp", - "someFullName", ImageStatus.CREATED); + ImageInfoRecord imageInfoRecord = new ImageInfoRecord("someName", + "2020-02-02", + "someDescription", + "someProject", + "someEndpoint", + "someUser", + "someApp", + "someInstance", + CloudProvider.AWS, + "someFullName", + ImageStatus.CREATED, + "private"); return Collections.singletonList(imageInfoRecord); } } diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/BillingServiceImplTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/BillingServiceImplTest.java index 5c9ec461b..b2345a8c8 100644 --- a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/BillingServiceImplTest.java +++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/BillingServiceImplTest.java @@ -56,6 +56,7 @@ import org.mockito.runners.MockitoJUnitRunner; import javax.ws.rs.core.GenericType; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -745,7 +746,19 @@ public class BillingServiceImplTest extends TestBase { private List<ImageInfoRecord> getImageInfoRecords() { return Collections.singletonList( - new ImageInfoRecord(IMAGE_NAME, IMAGE_DESCRIPTION, PROJECT, ENDPOINT, USER, IMAGE_APPLICATION, IMAGE_FULL_NAME, ImageStatus.CREATED) + new ImageInfoRecord( + IMAGE_NAME, + "2020-02-02", + IMAGE_DESCRIPTION, + PROJECT, + ENDPOINT, + USER, + IMAGE_APPLICATION, + EXPLORATORY_NAME, + CloudProvider.GENERAL, + IMAGE_FULL_NAME, + ImageStatus.CREATED, + "private") ); } } \ No newline at end of file diff --git a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImplTest.java b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImplTest.java index 78eb81f55..1af955aa8 100644 --- a/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImplTest.java +++ b/services/self-service/src/test/java/com/epam/datalab/backendapi/service/impl/ImageExploratoryServiceImplTest.java @@ -52,6 +52,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -308,8 +309,8 @@ public class ImageExploratoryServiceImplTest { } private ImageInfoRecord getImageInfoRecord() { - return new ImageInfoRecord("someName", "someDescription", "someProject", "someEndpoint", "someUser", "someApp", - "someFullName", ImageStatus.CREATED); + return new ImageInfoRecord("someName", "2020-02-02","someDescription", "someProject", "someEndpoint", "someUser", "someApp", + "someInstance",CloudProvider.GENERAL,"someFullName", ImageStatus.CREATED, "private"); } private Image fetchImage() { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
