+ +
+ Oops! No instances available... + + Try to sign in to access to protected instances. + +
+
+
set('App\Action\GroupAction', function (ContainerInterface $c) { }); $container->set('App\Action\InstanceListAction', function (ContainerInterface $c) { - return new App\Action\InstanceListAction($c->get('em')); + return new App\Action\InstanceListAction($c->get('em'), $c->get(SETTINGS)['token']); }); $container->set('App\Action\InstanceAction', function (ContainerInterface $c) { diff --git a/server/src/Action/AbstractAction.php b/server/src/Action/AbstractAction.php index eea4104..8bb4537 100644 --- a/server/src/Action/AbstractAction.php +++ b/server/src/Action/AbstractAction.php @@ -85,6 +85,40 @@ abstract class AbstractAction } } + /** + * @param ServerRequestInterface $request PSR-7 This object represents the HTTP request + * @param string $instanceName + * @param array $adminRoles + */ + protected function verifyInstanceAuthorization( + ServerRequestInterface $request, + string $instanceName, + array $adminRoles + ) { + $token = $request->getAttribute('token'); + if (!$token) { + // The user is not connected (401) + throw new HttpUnauthorizedException($request); + } + $roles = $token->realm_access->roles; + if (!$this->isAdmin($adminRoles, $roles)) { + $qb = $this->em->createQueryBuilder(); + $qb->select('i.name') + ->from('App\Entity\InstanceGroup', 'ig') + ->join('ig.instances', 'i') + ->where($qb->expr()->in('ig.role', $roles)) + ->andWhere($qb->expr()->eq('i.name', ':iname')); + $qb->setParameter('iname', $instanceName); + $r = $qb->getQuery()->getResult(); + if (count($r) < 1) { + throw new HttpForbiddenException( + $request, + 'You do not have the permission to access the instance : ' . $instanceName + ); + } + } + } + protected function isAdmin(array $adminRoles, $roles) { $admin = false; diff --git a/server/src/Action/DatasetFileExplorerAction.php b/server/src/Action/DatasetFileExplorerAction.php index 5dd9412..077b0be 100644 --- a/server/src/Action/DatasetFileExplorerAction.php +++ b/server/src/Action/DatasetFileExplorerAction.php @@ -82,6 +82,16 @@ final class DatasetFileExplorerAction extends AbstractAction ); } + // If instance is private and authorization enabled + $instance = $dataset->getDatasetFamily()->getInstance(); + if (!$instance->getPublic() && boolval($this->settings['enabled'])) { + $this->verifyInstanceAuthorization( + $request, + $instance->getName(), + explode(',', $this->settings['admin_roles']) + ); + } + // If dataset is private and authorization enabled if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { $this->verifyDatasetAuthorization( diff --git a/server/src/Action/DownloadArchiveAction.php b/server/src/Action/DownloadArchiveAction.php index 5be57fd..68920a8 100644 --- a/server/src/Action/DownloadArchiveAction.php +++ b/server/src/Action/DownloadArchiveAction.php @@ -90,6 +90,16 @@ final class DownloadArchiveAction extends AbstractAction ); } + // If instance is private and authorization enabled + $instance = $dataset->getDatasetFamily()->getInstance(); + if (!$instance->getPublic() && boolval($this->settings['enabled'])) { + $this->verifyInstanceAuthorization( + $request, + $instance->getName(), + explode(',', $this->settings['admin_roles']) + ); + } + // If dataset is private and authorization enabled if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { $this->verifyDatasetAuthorization( diff --git a/server/src/Action/DownloadFileAction.php b/server/src/Action/DownloadFileAction.php index ebce967..196a0ad 100644 --- a/server/src/Action/DownloadFileAction.php +++ b/server/src/Action/DownloadFileAction.php @@ -82,6 +82,16 @@ final class DownloadFileAction extends AbstractAction ); } + // If instance is private and authorization enabled + $instance = $dataset->getDatasetFamily()->getInstance(); + if (!$instance->getPublic() && boolval($this->settings['enabled'])) { + $this->verifyInstanceAuthorization( + $request, + $instance->getName(), + explode(',', $this->settings['admin_roles']) + ); + } + // If dataset is private and authorization enabled if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { $this->verifyDatasetAuthorization( diff --git a/server/src/Action/DownloadResultAction.php b/server/src/Action/DownloadResultAction.php index 6d5f7eb..41a996c 100644 --- a/server/src/Action/DownloadResultAction.php +++ b/server/src/Action/DownloadResultAction.php @@ -90,6 +90,16 @@ final class DownloadResultAction extends AbstractAction ); } + // If instance is private and authorization enabled + $instance = $dataset->getDatasetFamily()->getInstance(); + if (!$instance->getPublic() && boolval($this->settings['enabled'])) { + $this->verifyInstanceAuthorization( + $request, + $instance->getName(), + explode(',', $this->settings['admin_roles']) + ); + } + // If dataset is private and authorization enabled if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { $this->verifyDatasetAuthorization( diff --git a/server/src/Action/InstanceListAction.php b/server/src/Action/InstanceListAction.php index 9aa4c2a..1dc9f4c 100644 --- a/server/src/Action/InstanceListAction.php +++ b/server/src/Action/InstanceListAction.php @@ -14,6 +14,7 @@ namespace App\Action; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; +use Doctrine\ORM\EntityManagerInterface; use Slim\Exception\HttpBadRequestException; use App\Entity\Instance; @@ -23,6 +24,25 @@ use App\Entity\Instance; */ final class InstanceListAction extends AbstractAction { + /** + * Contains settings to handle Json Web Token + * + * @var array + */ + private $settings; + + /** + * Create the classe before call __invoke to execute the action + * + * @param EntityManagerInterface $em Doctrine Entity Manager Interface + * @param array $settings Settings about token + */ + public function __construct(EntityManagerInterface $em, array $settings) + { + parent::__construct($em); + $this->settings = $settings; + } + /** * `GET` Returns a list of all instances listed in the metamodel * `POST` Add a new instance @@ -43,7 +63,8 @@ final class InstanceListAction extends AbstractAction } if ($request->getMethod() === GET) { - $instances = $this->em->getRepository('App\Entity\Instance')->findAll(); + //$instances = $this->em->getRepository('App\Entity\Instance')->findAll(); + $instances = $this->getInstanceList($request->getAttribute('token')); $payload = json_encode($instances); } @@ -69,6 +90,35 @@ final class InstanceListAction extends AbstractAction return $response; } + private function getInstanceList($token) + { + $qb = $this->em->createQueryBuilder(); + $qb->select('i')->from('App\Entity\Instance', 'i'); + + if (boolval($this->settings['enabled'])) { + if (!$token) { + // If user is not connected return public instances + $qb->andWhere($qb->expr()->eq('i.public', 'true')); + } else { + $adminRoles = explode(',', $this->settings['admin_roles']); + $roles = $token->realm_access->roles; + if (!$this->isAdmin($adminRoles, $roles)) { + // If user is not an admin return public datasets + // And returns datasets from user's groups + $qb->andWhere($qb->expr()->eq('i.public', 'true')); + $qb2 = $this->em->createQueryBuilder(); + $qb2->select('i2.name') + ->from('App\Entity\InstanceGroup', 'ig') + ->join('ig.instances', 'i2') + ->where($qb2->expr()->in('ig.role', $roles)); + $qb->orWhere($qb->expr()->in('i.name', $qb2->getDQL())); + } + } + } + + return $qb->getQuery()->getResult(); + } + /** * Add a new instance into the metamodel * diff --git a/server/src/Action/SearchAction.php b/server/src/Action/SearchAction.php index 3050de3..52051a9 100644 --- a/server/src/Action/SearchAction.php +++ b/server/src/Action/SearchAction.php @@ -105,6 +105,16 @@ final class SearchAction extends AbstractAction ); } + // If instance is private and authorization enabled + $instance = $dataset->getDatasetFamily()->getInstance(); + if (!$instance->getPublic() && boolval($this->settings['enabled'])) { + $this->verifyInstanceAuthorization( + $request, + $instance->getName(), + explode(',', $this->settings['admin_roles']) + ); + } + // If dataset is private and authorization enabled if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { $this->verifyDatasetAuthorization( diff --git a/server/src/Action/StartTaskCreateArchiveAction.php b/server/src/Action/StartTaskCreateArchiveAction.php index e98bbf4..f9c0f7b 100644 --- a/server/src/Action/StartTaskCreateArchiveAction.php +++ b/server/src/Action/StartTaskCreateArchiveAction.php @@ -87,8 +87,20 @@ final class StartTaskCreateArchiveAction extends AbstractAction ); } - // If dataset is private and authorization enabled $token = ''; + + // If instance is private and authorization enabled + $instance = $dataset->getDatasetFamily()->getInstance(); + if (!$instance->getPublic() && boolval($this->settings['enabled'])) { + $this->verifyInstanceAuthorization( + $request, + $instance->getName(), + explode(',', $this->settings['admin_roles']) + ); + $token = $request->getHeader('Authorization')[0]; + } + + // If dataset is private and authorization enabled if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { $this->verifyDatasetAuthorization( $request, diff --git a/server/src/Action/StartTaskCreateResultAction.php b/server/src/Action/StartTaskCreateResultAction.php index 5773728..fc1e34e 100644 --- a/server/src/Action/StartTaskCreateResultAction.php +++ b/server/src/Action/StartTaskCreateResultAction.php @@ -87,8 +87,20 @@ final class StartTaskCreateResultAction extends AbstractAction ); } - // If dataset is private and authorization enabled $token = ''; + + // If instance is private and authorization enabled + $instance = $dataset->getDatasetFamily()->getInstance(); + if (!$instance->getPublic() && boolval($this->settings['enabled'])) { + $this->verifyInstanceAuthorization( + $request, + $instance->getName(), + explode(',', $this->settings['admin_roles']) + ); + $token = $request->getHeader('Authorization')[0]; + } + + // If dataset is private and authorization enabled if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { $this->verifyDatasetAuthorization( $request, @@ -97,7 +109,7 @@ final class StartTaskCreateResultAction extends AbstractAction ); $token = $request->getHeader('Authorization')[0]; } - + $queryParams = $request->getQueryParams(); // The parameter "a" is mandatory diff --git a/server/tests/Action/InstanceActionTest.php b/server/tests/Action/InstanceActionTest.php index 4f67cde..2b79c1c 100644 --- a/server/tests/Action/InstanceActionTest.php +++ b/server/tests/Action/InstanceActionTest.php @@ -82,6 +82,7 @@ final class InstanceActionTest extends TestCase 'description' => 'Test', 'display' => 10, 'data_path' => '/DEFAULT', + 'public' => true, 'portal_logo' => '', 'design_color' => '#7AC29A', 'design_background_color' => '#FFFFFF', diff --git a/server/tests/Action/InstanceListActionTest.php b/server/tests/Action/InstanceListActionTest.php index bfaa7b7..d263fcd 100644 --- a/server/tests/Action/InstanceListActionTest.php +++ b/server/tests/Action/InstanceListActionTest.php @@ -17,7 +17,9 @@ use Nyholm\Psr7\ServerRequest; use Nyholm\Psr7\Response; use Slim\Exception\HttpBadRequestException; use Doctrine\ORM\EntityManager; -use Doctrine\Persistence\ObjectRepository; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\Query\Expr; final class InstanceListActionTest extends TestCase { @@ -26,8 +28,12 @@ final class InstanceListActionTest extends TestCase protected function setUp(): void { + $settings = array( + 'enabled' => '0', + 'admin_role' => 'anis_admin' + ); $this->entityManager = $this->createMock(EntityManager::class); - $this->action = new \App\Action\InstanceListAction($this->entityManager); + $this->action = new \App\Action\InstanceListAction($this->entityManager, $settings); } public function testOptionsHttpMethod(): void @@ -39,9 +45,18 @@ final class InstanceListActionTest extends TestCase public function testGetAllInstances(): void { - $repository = $this->getObjectRepositoryMock(); - $repository->expects($this->once())->method('findAll'); - $this->entityManager->method('getRepository')->with('App\Entity\Instance')->willReturn($repository); + $expr = $this->getExprMock(); + $query = $this->getAbstractQueryMock(); + $query->expects($this->once())->method('getResult'); + + $queryBuilder = $this->getQueryBuilderMock(); + $queryBuilder->method('select')->willReturn($queryBuilder); + $queryBuilder->method('from')->willReturn($queryBuilder); + $queryBuilder->method('join')->willReturn($queryBuilder); + $queryBuilder->method('expr')->willReturn($expr); + $queryBuilder->expects($this->once())->method('getQuery')->willReturn($query); + + $this->entityManager->method('createQueryBuilder')->willReturn($queryBuilder); $request = $this->getRequest('GET'); ($this->action)($request, new Response(), array()); @@ -66,6 +81,7 @@ final class InstanceListActionTest extends TestCase 'description' => 'Test', 'display' => 10, 'data_path' => '/DEFAULT', + 'public' => true, 'portal_logo' => '', 'design_color' => '#7AC29A', 'design_background_color' => '#FFFFFF', @@ -96,10 +112,26 @@ final class InstanceListActionTest extends TestCase } /** - * @return ObjectRepository|\PHPUnit\Framework\MockObject\MockObject + * @return Expr|\PHPUnit\Framework\MockObject\MockObject + */ + private function getExprMock() + { + return $this->createMock(Expr::class); + } + + /** + * @return AbstractQuery|\PHPUnit\Framework\MockObject\MockObject + */ + private function getAbstractQueryMock() + { + return $this->createMock(AbstractQuery::class); + } + + /** + * @return QueryBuilder|\PHPUnit\Framework\MockObject\MockObject */ - private function getObjectRepositoryMock() + private function getQueryBuilderMock() { - return $this->createMock(ObjectRepository::class); + return $this->createMock(QueryBuilder::class); } } -- GitLab From cfb15e787cf29ea7302e931f0c54293def75b51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= Date: Wed, 23 Mar 2022 22:02:50 +0100 Subject: [PATCH 10/59] #25 => WIP --- .../dataset/dataset-form.component.html | 8 ++ .../dataset/dataset-form.component.ts | 15 ++++ .../dataset-by-family.component.spec.ts | 4 + .../dataset-card-doc.component.spec.ts | 2 + .../cone-search-plot-tab.component.html | 19 ++++ .../result/cone-search-plot-tab.component.ts | 48 ++++++++++ .../result/cone-search-plot.component.html | 1 + .../result/cone-search-plot.component.scss | 20 +++++ .../result/cone-search-plot.component.ts | 88 +++++++++++++++++++ .../result/download.component.spec.ts | 12 +++ .../search/components/result/index.ts | 4 + .../result/reminder.component.spec.ts | 8 ++ .../result/url-display.component.spec.ts | 8 ++ .../components/summary.component.spec.ts | 4 + .../search/containers/result.component.html | 9 ++ .../search/services/download.service.ts | 13 --- .../effects/search-multiple.effects.spec.ts | 2 + .../src/app/metamodel/models/dataset.model.ts | 2 + client/src/test-data.ts | 6 ++ conf-dev/create-db.sh | 10 +-- docker-compose.yml | 2 +- .../doctrine-proxy/__CG__AppEntityDataset.php | 48 +++++++++- server/src/Action/DatasetAction.php | 2 + server/src/Action/DatasetListAction.php | 2 + server/src/Entity/Dataset.php | 36 ++++++++ server/tests/Action/DatasetActionTest.php | 2 + server/tests/Action/DatasetListActionTest.php | 2 + 27 files changed, 356 insertions(+), 21 deletions(-) create mode 100644 client/src/app/instance/search/components/result/cone-search-plot-tab.component.html create mode 100644 client/src/app/instance/search/components/result/cone-search-plot-tab.component.ts create mode 100644 client/src/app/instance/search/components/result/cone-search-plot.component.html create mode 100644 client/src/app/instance/search/components/result/cone-search-plot.component.scss create mode 100644 client/src/app/instance/search/components/result/cone-search-plot.component.ts delete mode 100644 client/src/app/instance/search/services/download.service.ts diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html index 7798100..e1ec916 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html @@ -95,6 +95,14 @@
+
+ + +
+
+ + +
diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts index 9041adc..87b0f8f 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts @@ -50,6 +50,8 @@ export class DatasetFormComponent implements OnInit, OnChanges { cone_search_opened: new FormControl({value: false, disabled: true}), cone_search_column_ra: new FormControl({value: false, disabled: true}), cone_search_column_dec: new FormControl({value: false, disabled: true}), + cone_search_plot_enabled: new FormControl(false), + cone_search_plot_opened: new FormControl({value: false, disabled: true}), download_enabled: new FormControl(true), download_opened: new FormControl(false), download_csv: new FormControl(true), @@ -130,11 +132,24 @@ export class DatasetFormComponent implements OnInit, OnChanges { this.form.controls.cone_search_opened.enable(); this.form.controls.cone_search_column_ra.enable(); this.form.controls.cone_search_column_dec.enable(); + this.form.controls.cone_search_plot_enabled.enable(); + this.checkConeSearchPlotDisableOpened(); } else { this.form.controls.cone_search_opened.setValue(false); this.form.controls.cone_search_opened.disable(); this.form.controls.cone_search_column_ra.disable(); this.form.controls.cone_search_column_dec.disable(); + this.form.controls.cone_search_plot_enabled.disable(); + this.form.controls.cone_search_plot_opened.disable(); + } + } + + checkConeSearchPlotDisableOpened() { + if (this.form.controls.cone_search_plot_enabled.value) { + this.form.controls.cone_search_plot_opened.enable(); + } else { + this.form.controls.cone_search_plot_enabled.setValue(false); + this.form.controls.cone_search_plot_opened.disable(); } } diff --git a/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts b/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts index 17cec1c..9feede0 100644 --- a/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts +++ b/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts @@ -25,6 +25,8 @@ const DATASET_LIST: Dataset[] = [ cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -56,6 +58,8 @@ const DATASET_LIST: Dataset[] = [ cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts b/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts index 580c08b..264884a 100644 --- a/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts +++ b/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts @@ -32,6 +32,8 @@ const DATASET: Dataset = { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/search/components/result/cone-search-plot-tab.component.html b/client/src/app/instance/search/components/result/cone-search-plot-tab.component.html new file mode 100644 index 0000000..4c73ee0 --- /dev/null +++ b/client/src/app/instance/search/components/result/cone-search-plot-tab.component.html @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/client/src/app/instance/search/components/result/cone-search-plot-tab.component.ts b/client/src/app/instance/search/components/result/cone-search-plot-tab.component.ts new file mode 100644 index 0000000..f4a7ba6 --- /dev/null +++ b/client/src/app/instance/search/components/result/cone-search-plot-tab.component.ts @@ -0,0 +1,48 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; + +import { Dataset, Attribute } from 'src/app/metamodel/models'; +import { ConeSearch } from 'src/app/instance/store/models'; + +/** + * @class + * @classdesc Cone-search plot tab component. + */ +@Component({ + selector: 'app-cone-search-plot-tab', + templateUrl: 'cone-search-plot-tab.component.html', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ConeSearchPlotTabComponent { + @Input() datasetSelected: string; + @Input() datasetList: Dataset[]; + @Input() attributeList: Attribute[]; + @Input() coneSearch: ConeSearch; + @Input() data: any; + @Input() dataIsLoading: boolean; + @Input() dataIsLoaded: boolean; + + getData() { + const dataset = this.getDataset(); + const columnRa = this.attributeList.find(a => a.id === dataset.cone_search_column_ra); + const columnDec = this.attributeList.find(a => a.id === dataset.cone_search_column_dec); + return this.data.map(d => ({ "x": +d[columnRa.label], "y": +d[columnDec.label] })); + } + + /** + * Returns selected dataset for the search. + * + * @return Dataset + */ + getDataset(): Dataset { + return this.datasetList.find(dataset => dataset.name === this.datasetSelected); + } +} diff --git a/client/src/app/instance/search/components/result/cone-search-plot.component.html b/client/src/app/instance/search/components/result/cone-search-plot.component.html new file mode 100644 index 0000000..7519216 --- /dev/null +++ b/client/src/app/instance/search/components/result/cone-search-plot.component.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/client/src/app/instance/search/components/result/cone-search-plot.component.scss b/client/src/app/instance/search/components/result/cone-search-plot.component.scss new file mode 100644 index 0000000..b8d1c2b --- /dev/null +++ b/client/src/app/instance/search/components/result/cone-search-plot.component.scss @@ -0,0 +1,20 @@ +.horizontalGrid { + fill: none; + shape-rendering: crispEdges; + stroke: lightgrey; + stroke-width: 1px; +} + +.tooltip { + color: black; + background-color: whitesmoke; + border: 1px solid grey; + border-radius: 4px; + padding: 5px; +} + +.star { + fill: none; + stroke: #f78001; + stroke-width: 2px; +} diff --git a/client/src/app/instance/search/components/result/cone-search-plot.component.ts b/client/src/app/instance/search/components/result/cone-search-plot.component.ts new file mode 100644 index 0000000..d2fe651 --- /dev/null +++ b/client/src/app/instance/search/components/result/cone-search-plot.component.ts @@ -0,0 +1,88 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, ChangeDetectionStrategy, ViewEncapsulation, OnInit } from '@angular/core'; + +import * as d3 from 'd3'; + +import { ConeSearch } from 'src/app/instance/store/models'; + +/** + * @class + * @classdesc Cone-search plot component. + */ +@Component({ + selector: 'app-cone-search-plot', + templateUrl: 'cone-search-plot.component.html', + styleUrls: [ 'cone-search-plot.component.scss' ], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None +}) +export class ConeSearchPlotComponent implements OnInit { + @Input() coneSearch: ConeSearch; + @Input() data: {x: number, y: number}[]; + + // Interactive variables intialisation + margin = { top: 50, right: 50, bottom: 50 , left: 50 }; + width = 500; + height = 500; + degTorad = 0.0174532925; + x: d3.ScaleLinear; + y: d3.ScaleLinear; + + ngOnInit(): void { + this.coneSearchPlot(); + } + + coneSearchPlot(): void { + // Init SVG + const svg = d3.select('#plot').append('svg') + .attr('id', 'plot') + .attr('width', this.width + this.margin.left + this.margin.right) + .attr('height', this.height + this.margin.top + this.margin.bottom) + .append('g') + .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); + + // Set Domain + this.x = d3.scaleLinear().range([this.width, 0]); + this.y = d3.scaleLinear().range([this.height, 0]); + this.x.domain([this.coneSearch.ra - (this.coneSearch.radius / 3600) / Math.cos(this.coneSearch.dec * this.degTorad), this.coneSearch.ra + (this.coneSearch.radius / 3600) / Math.cos(this.coneSearch.dec * this.degTorad)]); + this.y.domain([this.coneSearch.dec - (this.coneSearch.radius / 3600), this.coneSearch.dec + (this.coneSearch.radius / 3600)]); + + // Add X axe + svg.append("g") + .attr("transform", "translate(0," + this.height + ")") + .call(d3.axisBottom(this.x)); + + // Add Y axe + svg.append("g") + .call(d3.axisLeft(this.y)); + + // Add data + svg.selectAll("circle") + .data(this.data) + .enter() + .append("rect") + .attr('class','star') + .attr("x", d => this.x(d.x) - 3) + .attr("y", d => this.y(d.y) - 3) + .attr('width', 6) + .attr('height', 6) + .on('mouseover', function (d) { + const tooltip = d3.select('body') + .append('div') + .attr('class', 'tooltip') + .style('opacity', 1); + tooltip.html('Ra: ' + d.x + '
Dec: ' + d.y) + .style('left', (d3.event.pageX + 10) + 'px') + .style('top', (d3.event.pageY - 5) + 'px'); + }) + .on('mouseout', () => { d3.select('.tooltip').remove(); }); + } +} \ No newline at end of file diff --git a/client/src/app/instance/search/components/result/download.component.spec.ts b/client/src/app/instance/search/components/result/download.component.spec.ts index 2a53a46..7533825 100644 --- a/client/src/app/instance/search/components/result/download.component.spec.ts +++ b/client/src/app/instance/search/components/result/download.component.spec.ts @@ -49,6 +49,8 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -80,6 +82,8 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: false, download_opened: false, download_csv: false, @@ -120,6 +124,8 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -151,6 +157,8 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: false, download_opened: false, download_csv: false, @@ -191,6 +199,8 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -222,6 +232,8 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: false, download_opened: false, download_csv: false, diff --git a/client/src/app/instance/search/components/result/index.ts b/client/src/app/instance/search/components/result/index.ts index b39de14..f0fea02 100644 --- a/client/src/app/instance/search/components/result/index.ts +++ b/client/src/app/instance/search/components/result/index.ts @@ -5,6 +5,8 @@ import { UrlDisplayComponent } from './url-display.component'; import { DatatableComponent } from './datatable.component'; import { DatatableActionsComponent } from './datatable-actions.component'; import { DownloadFileTabComponent } from './download-file-tab.component'; +import { ConeSearchPlotTabComponent } from './cone-search-plot-tab.component'; +import { ConeSearchPlotComponent } from './cone-search-plot.component'; import { rendererComponents } from './renderer'; export const resultComponents = [ @@ -15,5 +17,7 @@ export const resultComponents = [ DatatableComponent, DatatableActionsComponent, DownloadFileTabComponent, + ConeSearchPlotTabComponent, + ConeSearchPlotComponent, rendererComponents ]; diff --git a/client/src/app/instance/search/components/result/reminder.component.spec.ts b/client/src/app/instance/search/components/result/reminder.component.spec.ts index b39d431..8ba8e69 100644 --- a/client/src/app/instance/search/components/result/reminder.component.spec.ts +++ b/client/src/app/instance/search/components/result/reminder.component.spec.ts @@ -48,6 +48,8 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -79,6 +81,8 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -119,6 +123,8 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -150,6 +156,8 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/search/components/result/url-display.component.spec.ts b/client/src/app/instance/search/components/result/url-display.component.spec.ts index 93e9eaa..05822c7 100644 --- a/client/src/app/instance/search/components/result/url-display.component.spec.ts +++ b/client/src/app/instance/search/components/result/url-display.component.spec.ts @@ -52,6 +52,8 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -83,6 +85,8 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -123,6 +127,8 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -154,6 +160,8 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/search/components/summary.component.spec.ts b/client/src/app/instance/search/components/summary.component.spec.ts index 52daf77..3188cbf 100644 --- a/client/src/app/instance/search/components/summary.component.spec.ts +++ b/client/src/app/instance/search/components/summary.component.spec.ts @@ -48,6 +48,8 @@ describe('[Instance][Search][Component] SummaryComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -79,6 +81,8 @@ describe('[Instance][Search][Component] SummaryComponent', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/search/containers/result.component.html b/client/src/app/instance/search/containers/result.component.html index f35f242..2734d84 100644 --- a/client/src/app/instance/search/containers/result.component.html +++ b/client/src/app/instance/search/containers/result.component.html @@ -71,6 +71,15 @@ [outputList]="outputList | async" [coneSearch]="coneSearch | async"> + + { - return this.http.get(url, { - reportProgress: true, - responseType: 'blob' - }); - } -} diff --git a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts index 7638675..88b5adb 100644 --- a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts +++ b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts @@ -252,6 +252,8 @@ describe('[Instance][Store] SearchMultipleEffects', () => { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/metamodel/models/dataset.model.ts b/client/src/app/metamodel/models/dataset.model.ts index 43a0dd3..4e44417 100644 --- a/client/src/app/metamodel/models/dataset.model.ts +++ b/client/src/app/metamodel/models/dataset.model.ts @@ -26,6 +26,8 @@ export interface Dataset { cone_search_opened: boolean; cone_search_column_ra: number; cone_search_column_dec: number; + cone_search_plot_enabled: boolean; + cone_search_plot_opened: boolean; download_enabled: boolean; download_opened: boolean; download_csv: boolean; diff --git a/client/src/test-data.ts b/client/src/test-data.ts index 6182944..314b31f 100644 --- a/client/src/test-data.ts +++ b/client/src/test-data.ts @@ -205,6 +205,8 @@ export const DATASET_LIST: Dataset[] = [ cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -236,6 +238,8 @@ export const DATASET_LIST: Dataset[] = [ cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -269,6 +273,8 @@ export const DATASET: Dataset = { cone_search_opened: true, cone_search_column_ra: 1, cone_search_column_dec: 2, + cone_search_plot_enabled: false, + cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh index fe86c76..ddce165 100644 --- a/conf-dev/create-db.sh +++ b/conf-dev/create-db.sh @@ -73,11 +73,11 @@ curl -d '{"label":"SVOM dataset family","display":20,"opened":true}' --header 'C curl -d '{"label":"IRiS dataset family","display":30,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/instance/default/dataset-family # Add datasets -curl -d '{"name":"vipers_dr2_w1","table_ref":"aspic_vipers_dr2_w1","label":"VIPERS-W1 (DR2)","description":"VIPERS W1 dataset","display":10,"data_path":"\/ASPIC\/VIPERS_DR2","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"anis_survey"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/1/dataset -curl -d '{"name":"sp_cards","table_ref":"sp_cards","label":"SP Metadata","description":"Contains metadata of scientific products (Core Program & General Program)","display":30,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"svom"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/2/dataset -curl -d '{"name":"observations","table_ref":"v_observation","label":"IRiS obs","description":"IRiS observations","display":10,"data_path":"\/IRIS\/observations","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"iris"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/3/dataset -curl -d '{"name":"vvds_f02_udeep","table_ref":"aspic_vvds_f02_udeep","label":"VVDS2h Ultra Deep","description":"VVDS2h Ultra Deep","display":20,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"anis_survey"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/1/dataset -curl -d '{"name":"products","table_ref":"products","label":"Scientific Products","description":"SR3 & SR4 products list","display":20,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"svom"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/2/dataset +curl -d '{"name":"vipers_dr2_w1","table_ref":"aspic_vipers_dr2_w1","label":"VIPERS-W1 (DR2)","description":"VIPERS W1 dataset","display":10,"data_path":"\/ASPIC\/VIPERS_DR2","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"cone_search_plot_opened":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"anis_survey"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/1/dataset +curl -d '{"name":"sp_cards","table_ref":"sp_cards","label":"SP Metadata","description":"Contains metadata of scientific products (Core Program & General Program)","display":30,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"cone_search_plot_opened":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"svom"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/2/dataset +curl -d '{"name":"observations","table_ref":"v_observation","label":"IRiS obs","description":"IRiS observations","display":10,"data_path":"\/IRIS\/observations","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"cone_search_plot_opened":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"iris"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/3/dataset +curl -d '{"name":"vvds_f02_udeep","table_ref":"aspic_vvds_f02_udeep","label":"VVDS2h Ultra Deep","description":"VVDS2h Ultra Deep","display":20,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"cone_search_plot_opened":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"anis_survey"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/1/dataset +curl -d '{"name":"products","table_ref":"products","label":"Scientific Products","description":"SR3 & SR4 products list","display":20,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"cone_search_plot_opened":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"svom"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/2/dataset # Add vipers_dr2_w1 attributes curl -d '{"label":"Default","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/vipers_dr2_w1/criteria-family diff --git a/docker-compose.yml b/docker-compose.yml index e980ced..06b8074 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: SSO_AUTH_URL: "http://localhost:8180/auth" SSO_REALM: "anis" SSO_CLIENT_ID: "anis-client" - TOKEN_ENABLED: 1 + TOKEN_ENABLED: 0 TOKEN_JWKS_URL: "http://keycloak:8180/auth/realms/anis/protocol/openid-connect/certs" TOKEN_ADMIN_ROLES: anis_admin,superuser RMQ_HOST: rmq diff --git a/server/doctrine-proxy/__CG__AppEntityDataset.php b/server/doctrine-proxy/__CG__AppEntityDataset.php index 30afd80..eb60eb6 100644 --- a/server/doctrine-proxy/__CG__AppEntityDataset.php +++ b/server/doctrine-proxy/__CG__AppEntityDataset.php @@ -67,10 +67,10 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy public function __sleep() { if ($this->__isInitialized__) { - return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; + return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'coneSearchPlotOpened', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; } - return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; + return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'coneSearchPlotOpened', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; } /** @@ -467,6 +467,50 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy return parent::setConeSearchColumnDec($coneSearchColumnDec); } + /** + * {@inheritDoc} + */ + public function getConeSearchPlotEnabled() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConeSearchPlotEnabled', []); + + return parent::getConeSearchPlotEnabled(); + } + + /** + * {@inheritDoc} + */ + public function setConeSearchPlotEnabled($coneSearchPlotEnabled) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConeSearchPlotEnabled', [$coneSearchPlotEnabled]); + + return parent::setConeSearchPlotEnabled($coneSearchPlotEnabled); + } + + /** + * {@inheritDoc} + */ + public function getConeSearchPlotOpened() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConeSearchPlotOpened', []); + + return parent::getConeSearchPlotOpened(); + } + + /** + * {@inheritDoc} + */ + public function setConeSearchPlotOpened($coneSearchPlotOpened) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConeSearchPlotOpened', [$coneSearchPlotOpened]); + + return parent::setConeSearchPlotOpened($coneSearchPlotOpened); + } + /** * {@inheritDoc} */ diff --git a/server/src/Action/DatasetAction.php b/server/src/Action/DatasetAction.php index bef65e7..5d5edcc 100644 --- a/server/src/Action/DatasetAction.php +++ b/server/src/Action/DatasetAction.php @@ -127,6 +127,8 @@ final class DatasetAction extends AbstractAction $dataset->setConeSearchOpened($parsedBody['cone_search_opened']); $dataset->setConeSearchColumnRa($parsedBody['cone_search_column_ra']); $dataset->setConeSearchColumnDec($parsedBody['cone_search_column_dec']); + $dataset->setConeSearchPlotEnabled($parsedBody['cone_search_plot_enabled']); + $dataset->setConeSearchPlotOpened($parsedBody['cone_search_plot_opened']); $dataset->setDownloadEnabled($parsedBody['download_enabled']); $dataset->setDownloadOpened($parsedBody['download_opened']); $dataset->setDownloadCsv($parsedBody['download_csv']); diff --git a/server/src/Action/DatasetListAction.php b/server/src/Action/DatasetListAction.php index 870ef82..83fcef8 100644 --- a/server/src/Action/DatasetListAction.php +++ b/server/src/Action/DatasetListAction.php @@ -131,6 +131,8 @@ final class DatasetListAction extends AbstractAction $dataset->setConeSearchOpened($parsedBody['cone_search_opened']); $dataset->setConeSearchColumnRa($parsedBody['cone_search_column_ra']); $dataset->setConeSearchColumnDec($parsedBody['cone_search_column_dec']); + $dataset->setConeSearchPlotEnabled($parsedBody['cone_search_plot_enabled']); + $dataset->setConeSearchPlotOpened($parsedBody['cone_search_plot_opened']); $dataset->setDownloadEnabled($parsedBody['download_enabled']); $dataset->setDownloadOpened($parsedBody['download_opened']); $dataset->setDownloadCsv($parsedBody['download_csv']); diff --git a/server/src/Entity/Dataset.php b/server/src/Entity/Dataset.php index 1b90e75..20aff05 100644 --- a/server/src/Entity/Dataset.php +++ b/server/src/Entity/Dataset.php @@ -115,6 +115,20 @@ class Dataset implements \JsonSerializable */ protected $coneSearchColumnDec; + /** + * @var bool + * + * @Column(type="boolean", name="cone_search_plot_enabled", nullable=false) + */ + protected $coneSearchPlotEnabled; + + /** + * @var bool + * + * @Column(type="boolean", name="cone_search_plot_opened", nullable=false) + */ + protected $coneSearchPlotOpened; + /** * @var bool * @@ -365,6 +379,26 @@ class Dataset implements \JsonSerializable $this->coneSearchColumnDec = $coneSearchColumnDec; } + public function getConeSearchPlotEnabled() + { + return $this->coneSearchPlotEnabled; + } + + public function setConeSearchPlotEnabled($coneSearchPlotEnabled) + { + $this->coneSearchPlotEnabled = $coneSearchPlotEnabled; + } + + public function getConeSearchPlotOpened() + { + return $this->coneSearchPlotOpened; + } + + public function setConeSearchPlotOpened($coneSearchPlotOpened) + { + $this->coneSearchPlotOpened = $coneSearchPlotOpened; + } + public function getDownloadEnabled() { return $this->downloadEnabled; @@ -536,6 +570,8 @@ class Dataset implements \JsonSerializable 'cone_search_opened' => $this->getConeSearchOpened(), 'cone_search_column_ra' => $this->getConeSearchColumnRa(), 'cone_search_column_dec' => $this->getConeSearchColumnDec(), + 'cone_search_plot_enabled' => $this->getConeSearchPlotEnabled(), + 'cone_search_plot_opened' => $this->getConeSearchPlotOpened(), 'download_enabled' => $this->getDownloadEnabled(), 'download_opened' => $this->getDownloadOpened(), 'download_csv' => $this->getDownloadCsv(), diff --git a/server/tests/Action/DatasetActionTest.php b/server/tests/Action/DatasetActionTest.php index dc291d4..4624bf6 100644 --- a/server/tests/Action/DatasetActionTest.php +++ b/server/tests/Action/DatasetActionTest.php @@ -133,6 +133,8 @@ final class DatasetActionTest extends TestCase 'cone_search_opened' => true, 'cone_search_column_ra' => null, 'cone_search_column_dec' => null, + 'cone_search_plot_enabled' => false, + 'cone_search_plot_opened' => false, 'download_enabled' => true, 'download_opened' => false, 'download_csv' => true, diff --git a/server/tests/Action/DatasetListActionTest.php b/server/tests/Action/DatasetListActionTest.php index 6d77b5b..d7e4a3a 100644 --- a/server/tests/Action/DatasetListActionTest.php +++ b/server/tests/Action/DatasetListActionTest.php @@ -129,6 +129,8 @@ final class DatasetListActionTest extends TestCase 'cone_search_opened' => true, 'cone_search_column_ra' => null, 'cone_search_column_dec' => null, + 'cone_search_plot_enabled' => false, + 'cone_search_plot_opened' => false, 'download_enabled' => true, 'download_opened' => false, 'download_csv' => true, -- GitLab From 2f4517c306c8d3244f49081535eddf50a33b67ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= Date: Thu, 24 Mar 2022 17:32:32 +0100 Subject: [PATCH 11/59] WIP => plot --- .../result/cone-search-plot.component.ts | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/client/src/app/instance/search/components/result/cone-search-plot.component.ts b/client/src/app/instance/search/components/result/cone-search-plot.component.ts index d2fe651..139e9a7 100644 --- a/client/src/app/instance/search/components/result/cone-search-plot.component.ts +++ b/client/src/app/instance/search/components/result/cone-search-plot.component.ts @@ -40,6 +40,17 @@ export class ConeSearchPlotComponent implements OnInit { this.coneSearchPlot(); } + getHrefBackgroundImage() { + //const scale = this.width / (this.rad * 2); + const scale = this.coneSearch.radius / this.width; // arcsec/pix + return `https://skyserver.sdss.org/dr16/SkyServerWS/ImgCutout/getjpeg?TaskName=Skyserver.Chart.Image + &ra=${this.coneSearch.ra} + &dec=${this.coneSearch.dec} + &scale=${scale} + &width=${this.width} + &height=${this.height}`; + } + coneSearchPlot(): void { // Init SVG const svg = d3.select('#plot').append('svg') @@ -49,11 +60,16 @@ export class ConeSearchPlotComponent implements OnInit { .append('g') .attr('transform', 'translate(' + this.margin.left + ',' + this.margin.top + ')'); - // Set Domain - this.x = d3.scaleLinear().range([this.width, 0]); - this.y = d3.scaleLinear().range([this.height, 0]); - this.x.domain([this.coneSearch.ra - (this.coneSearch.radius / 3600) / Math.cos(this.coneSearch.dec * this.degTorad), this.coneSearch.ra + (this.coneSearch.radius / 3600) / Math.cos(this.coneSearch.dec * this.degTorad)]); - this.y.domain([this.coneSearch.dec - (this.coneSearch.radius / 3600), this.coneSearch.dec + (this.coneSearch.radius / 3600)]); + // Set Domain RA->DEG, DEC->DEG, RADIUS->ARCSEC + const coneSearchDomain = this.getConeSearchDomain(); + this.x = d3.scaleLinear().range([this.width, 0]).domain([coneSearchDomain.raMin, coneSearchDomain.raMax]); + this.y = d3.scaleLinear().range([this.height, 0]).domain([coneSearchDomain.decMin, coneSearchDomain.decMax]); + + // Background image + svg.append('image') + .attr('xlink:href', this.getHrefBackgroundImage()) + .attr('width', this.width) + .attr('height', this.height); // Add X axe svg.append("g") @@ -85,4 +101,17 @@ export class ConeSearchPlotComponent implements OnInit { }) .on('mouseout', () => { d3.select('.tooltip').remove(); }); } + + private getConeSearchDomain() { + const radiusInDegrees = this.coneSearch.radius / 3600; + + const decMin = this.coneSearch.dec - radiusInDegrees; + const decMax = this.coneSearch.dec + radiusInDegrees; + + const raCorrectedRadius = radiusInDegrees / Math.cos(this.degTorad * (Math.abs(this.coneSearch.dec) + radiusInDegrees)); + const raMin = this.coneSearch.ra - raCorrectedRadius; + const raMax = this.coneSearch.ra + raCorrectedRadius; + + return { raMin, raMax, decMin, decMax }; + } } \ No newline at end of file -- GitLab From 276c39e4b6eff511835ad32c3da74a449d88fdd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= Date: Fri, 25 Mar 2022 11:00:23 +0100 Subject: [PATCH 12/59] Del cone search plot opened property --- .../dataset/dataset-form.component.html | 4 -- .../dataset/dataset-form.component.ts | 1 - .../dataset-by-family.component.spec.ts | 2 - .../dataset-card-doc.component.spec.ts | 1 - .../cone-search-plot-tab.component.html | 19 ------ .../result/cone-search-plot-tab.component.ts | 48 -------------- .../result/datatable-actions.component.ts | 13 +--- .../result/datatable-tab.component.html | 65 ++++++++++--------- .../result/datatable-tab.component.ts | 16 +++++ .../result/download.component.spec.ts | 6 -- .../search/components/result/index.ts | 2 - .../result/reminder.component.spec.ts | 4 -- .../result/url-display.component.spec.ts | 4 -- .../components/summary.component.spec.ts | 2 - .../search/containers/result.component.html | 10 +-- .../src/app/metamodel/models/dataset.model.ts | 1 - client/src/test-data.ts | 3 - conf-dev/create-db.sh | 10 +-- server/src/Action/DatasetAction.php | 1 - server/src/Action/DatasetListAction.php | 1 - server/src/Entity/Dataset.php | 18 ----- server/tests/Action/DatasetActionTest.php | 1 - server/tests/Action/DatasetListActionTest.php | 1 - 23 files changed, 58 insertions(+), 175 deletions(-) delete mode 100644 client/src/app/instance/search/components/result/cone-search-plot-tab.component.html delete mode 100644 client/src/app/instance/search/components/result/cone-search-plot-tab.component.ts diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html index e1ec916..6c2a8cf 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html @@ -99,10 +99,6 @@
-
- - -
diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts index 87b0f8f..3e7080a 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts @@ -51,7 +51,6 @@ export class DatasetFormComponent implements OnInit, OnChanges { cone_search_column_ra: new FormControl({value: false, disabled: true}), cone_search_column_dec: new FormControl({value: false, disabled: true}), cone_search_plot_enabled: new FormControl(false), - cone_search_plot_opened: new FormControl({value: false, disabled: true}), download_enabled: new FormControl(true), download_opened: new FormControl(false), download_csv: new FormControl(true), diff --git a/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts b/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts index 9feede0..3878a0c 100644 --- a/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts +++ b/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts @@ -26,7 +26,6 @@ const DATASET_LIST: Dataset[] = [ cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -59,7 +58,6 @@ const DATASET_LIST: Dataset[] = [ cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts b/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts index 264884a..43226ca 100644 --- a/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts +++ b/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts @@ -33,7 +33,6 @@ const DATASET: Dataset = { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/search/components/result/cone-search-plot-tab.component.html b/client/src/app/instance/search/components/result/cone-search-plot-tab.component.html deleted file mode 100644 index 4c73ee0..0000000 --- a/client/src/app/instance/search/components/result/cone-search-plot-tab.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - diff --git a/client/src/app/instance/search/components/result/cone-search-plot-tab.component.ts b/client/src/app/instance/search/components/result/cone-search-plot-tab.component.ts deleted file mode 100644 index f4a7ba6..0000000 --- a/client/src/app/instance/search/components/result/cone-search-plot-tab.component.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * This file is part of Anis Client. - * - * @copyright Laboratoire d'Astrophysique de Marseille / CNRS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; - -import { Dataset, Attribute } from 'src/app/metamodel/models'; -import { ConeSearch } from 'src/app/instance/store/models'; - -/** - * @class - * @classdesc Cone-search plot tab component. - */ -@Component({ - selector: 'app-cone-search-plot-tab', - templateUrl: 'cone-search-plot-tab.component.html', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ConeSearchPlotTabComponent { - @Input() datasetSelected: string; - @Input() datasetList: Dataset[]; - @Input() attributeList: Attribute[]; - @Input() coneSearch: ConeSearch; - @Input() data: any; - @Input() dataIsLoading: boolean; - @Input() dataIsLoaded: boolean; - - getData() { - const dataset = this.getDataset(); - const columnRa = this.attributeList.find(a => a.id === dataset.cone_search_column_ra); - const columnDec = this.attributeList.find(a => a.id === dataset.cone_search_column_dec); - return this.data.map(d => ({ "x": +d[columnRa.label], "y": +d[columnDec.label] })); - } - - /** - * Returns selected dataset for the search. - * - * @return Dataset - */ - getDataset(): Dataset { - return this.datasetList.find(dataset => dataset.name === this.datasetSelected); - } -} diff --git a/client/src/app/instance/search/components/result/datatable-actions.component.ts b/client/src/app/instance/search/components/result/datatable-actions.component.ts index 58331ce..ac49911 100644 --- a/client/src/app/instance/search/components/result/datatable-actions.component.ts +++ b/client/src/app/instance/search/components/result/datatable-actions.component.ts @@ -1,7 +1,6 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { Dataset, Attribute } from 'src/app/metamodel/models'; -import { Criterion, ConeSearch } from 'src/app/instance/store/models'; +import { Dataset } from 'src/app/metamodel/models'; @Component({ selector: 'app-datatable-actions', @@ -11,11 +10,6 @@ export class DatatableActionsComponent { @Input() selectedData: any[] = []; @Input() datasetSelected: string; @Input() datasetList: Dataset[]; - @Input() attributeList: Attribute[]; - @Input() criteriaList: Criterion[]; - @Input() outputList: number[]; - @Input() coneSearch: ConeSearch; - @Input() dataLength: number; @Input() sampRegistered: boolean; @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean, broadcastVo: boolean }> = new EventEmitter(); @Output() startTaskCreateArchive: EventEmitter<{ selectedData: boolean }> = new EventEmitter(); @@ -35,11 +29,6 @@ export class DatatableActionsComponent { return this.datasetList.find(d => d.name === this.datasetSelected); } - getCriterionSelectedData() { - const attributeId = this.attributeList.find(a => a.search_flag === 'ID'); - return `${attributeId.id}::in::${this.selectedData.join('|')}`; - } - downloadResult(format: string) { this.startTaskCreateResult.emit({ format, diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.html b/client/src/app/instance/search/components/result/datatable-tab.component.html index c40a535..1eaf244 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.html +++ b/client/src/app/instance/search/components/result/datatable-tab.component.html @@ -8,35 +8,40 @@ - - - - +
+
+ + +
+
+ + + + +
+
diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.ts b/client/src/app/instance/search/components/result/datatable-tab.component.ts index 97335f0..8d3b912 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.ts +++ b/client/src/app/instance/search/components/result/datatable-tab.component.ts @@ -43,4 +43,20 @@ export class DatatableTabComponent { @Output() startTaskCreateResult: EventEmitter<{ format: string, selectedData: boolean, broadcastVo: boolean }> = new EventEmitter(); @Output() startTaskCreateArchive: EventEmitter<{ selectedData: boolean }> = new EventEmitter(); @Output() downloadFile: EventEmitter<{url: string, fileId: string, datasetName: string, filename: string}> = new EventEmitter(); + + getData() { + const dataset = this.getDataset(); + const columnRa = this.attributeList.find(a => a.id === dataset.cone_search_column_ra); + const columnDec = this.attributeList.find(a => a.id === dataset.cone_search_column_dec); + return this.data.map(d => ({ "x": +d[columnRa.label], "y": +d[columnDec.label] })); + } + + /** + * Returns selected dataset for the search. + * + * @return Dataset + */ + getDataset(): Dataset { + return this.datasetList.find(dataset => dataset.name === this.datasetSelected); + } } diff --git a/client/src/app/instance/search/components/result/download.component.spec.ts b/client/src/app/instance/search/components/result/download.component.spec.ts index 7533825..a98834b 100644 --- a/client/src/app/instance/search/components/result/download.component.spec.ts +++ b/client/src/app/instance/search/components/result/download.component.spec.ts @@ -50,7 +50,6 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -83,7 +82,6 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: false, download_opened: false, download_csv: false, @@ -125,7 +123,6 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -158,7 +155,6 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: false, download_opened: false, download_csv: false, @@ -200,7 +196,6 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -233,7 +228,6 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: false, download_opened: false, download_csv: false, diff --git a/client/src/app/instance/search/components/result/index.ts b/client/src/app/instance/search/components/result/index.ts index f0fea02..80f3a16 100644 --- a/client/src/app/instance/search/components/result/index.ts +++ b/client/src/app/instance/search/components/result/index.ts @@ -5,7 +5,6 @@ import { UrlDisplayComponent } from './url-display.component'; import { DatatableComponent } from './datatable.component'; import { DatatableActionsComponent } from './datatable-actions.component'; import { DownloadFileTabComponent } from './download-file-tab.component'; -import { ConeSearchPlotTabComponent } from './cone-search-plot-tab.component'; import { ConeSearchPlotComponent } from './cone-search-plot.component'; import { rendererComponents } from './renderer'; @@ -17,7 +16,6 @@ export const resultComponents = [ DatatableComponent, DatatableActionsComponent, DownloadFileTabComponent, - ConeSearchPlotTabComponent, ConeSearchPlotComponent, rendererComponents ]; diff --git a/client/src/app/instance/search/components/result/reminder.component.spec.ts b/client/src/app/instance/search/components/result/reminder.component.spec.ts index 8ba8e69..6e648e0 100644 --- a/client/src/app/instance/search/components/result/reminder.component.spec.ts +++ b/client/src/app/instance/search/components/result/reminder.component.spec.ts @@ -49,7 +49,6 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -82,7 +81,6 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -124,7 +122,6 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -157,7 +154,6 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/search/components/result/url-display.component.spec.ts b/client/src/app/instance/search/components/result/url-display.component.spec.ts index 05822c7..e09ba2f 100644 --- a/client/src/app/instance/search/components/result/url-display.component.spec.ts +++ b/client/src/app/instance/search/components/result/url-display.component.spec.ts @@ -53,7 +53,6 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -86,7 +85,6 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -128,7 +126,6 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -161,7 +158,6 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/search/components/summary.component.spec.ts b/client/src/app/instance/search/components/summary.component.spec.ts index 3188cbf..1a49dfa 100644 --- a/client/src/app/instance/search/components/summary.component.spec.ts +++ b/client/src/app/instance/search/components/summary.component.spec.ts @@ -49,7 +49,6 @@ describe('[Instance][Search][Component] SummaryComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, @@ -82,7 +81,6 @@ describe('[Instance][Search][Component] SummaryComponent', () => { cone_search_column_ra: 1, cone_search_column_dec: 2, cone_search_plot_enabled: false, - cone_search_plot_opened: false, download_enabled: true, download_opened: true, download_csv: true, diff --git a/client/src/app/instance/search/containers/result.component.html b/client/src/app/instance/search/containers/result.component.html index 2734d84..5237bf3 100644 --- a/client/src/app/instance/search/containers/result.component.html +++ b/client/src/app/instance/search/containers/result.component.html @@ -71,15 +71,6 @@ [outputList]="outputList | async" [coneSearch]="coneSearch | async"> - - setConeSearchColumnRa($parsedBody['cone_search_column_ra']); $dataset->setConeSearchColumnDec($parsedBody['cone_search_column_dec']); $dataset->setConeSearchPlotEnabled($parsedBody['cone_search_plot_enabled']); - $dataset->setConeSearchPlotOpened($parsedBody['cone_search_plot_opened']); $dataset->setDownloadEnabled($parsedBody['download_enabled']); $dataset->setDownloadOpened($parsedBody['download_opened']); $dataset->setDownloadCsv($parsedBody['download_csv']); diff --git a/server/src/Action/DatasetListAction.php b/server/src/Action/DatasetListAction.php index 83fcef8..9f73779 100644 --- a/server/src/Action/DatasetListAction.php +++ b/server/src/Action/DatasetListAction.php @@ -132,7 +132,6 @@ final class DatasetListAction extends AbstractAction $dataset->setConeSearchColumnRa($parsedBody['cone_search_column_ra']); $dataset->setConeSearchColumnDec($parsedBody['cone_search_column_dec']); $dataset->setConeSearchPlotEnabled($parsedBody['cone_search_plot_enabled']); - $dataset->setConeSearchPlotOpened($parsedBody['cone_search_plot_opened']); $dataset->setDownloadEnabled($parsedBody['download_enabled']); $dataset->setDownloadOpened($parsedBody['download_opened']); $dataset->setDownloadCsv($parsedBody['download_csv']); diff --git a/server/src/Entity/Dataset.php b/server/src/Entity/Dataset.php index 20aff05..3c78fdd 100644 --- a/server/src/Entity/Dataset.php +++ b/server/src/Entity/Dataset.php @@ -122,13 +122,6 @@ class Dataset implements \JsonSerializable */ protected $coneSearchPlotEnabled; - /** - * @var bool - * - * @Column(type="boolean", name="cone_search_plot_opened", nullable=false) - */ - protected $coneSearchPlotOpened; - /** * @var bool * @@ -389,16 +382,6 @@ class Dataset implements \JsonSerializable $this->coneSearchPlotEnabled = $coneSearchPlotEnabled; } - public function getConeSearchPlotOpened() - { - return $this->coneSearchPlotOpened; - } - - public function setConeSearchPlotOpened($coneSearchPlotOpened) - { - $this->coneSearchPlotOpened = $coneSearchPlotOpened; - } - public function getDownloadEnabled() { return $this->downloadEnabled; @@ -571,7 +554,6 @@ class Dataset implements \JsonSerializable 'cone_search_column_ra' => $this->getConeSearchColumnRa(), 'cone_search_column_dec' => $this->getConeSearchColumnDec(), 'cone_search_plot_enabled' => $this->getConeSearchPlotEnabled(), - 'cone_search_plot_opened' => $this->getConeSearchPlotOpened(), 'download_enabled' => $this->getDownloadEnabled(), 'download_opened' => $this->getDownloadOpened(), 'download_csv' => $this->getDownloadCsv(), diff --git a/server/tests/Action/DatasetActionTest.php b/server/tests/Action/DatasetActionTest.php index 4624bf6..b33db34 100644 --- a/server/tests/Action/DatasetActionTest.php +++ b/server/tests/Action/DatasetActionTest.php @@ -134,7 +134,6 @@ final class DatasetActionTest extends TestCase 'cone_search_column_ra' => null, 'cone_search_column_dec' => null, 'cone_search_plot_enabled' => false, - 'cone_search_plot_opened' => false, 'download_enabled' => true, 'download_opened' => false, 'download_csv' => true, diff --git a/server/tests/Action/DatasetListActionTest.php b/server/tests/Action/DatasetListActionTest.php index d7e4a3a..d6e843b 100644 --- a/server/tests/Action/DatasetListActionTest.php +++ b/server/tests/Action/DatasetListActionTest.php @@ -130,7 +130,6 @@ final class DatasetListActionTest extends TestCase 'cone_search_column_ra' => null, 'cone_search_column_dec' => null, 'cone_search_plot_enabled' => false, - 'cone_search_plot_opened' => false, 'download_enabled' => true, 'download_opened' => false, 'download_csv' => true, -- GitLab From 8dedb94272b3b4f3e7ad40b6f3fab289357d4372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= Date: Fri, 25 Mar 2022 11:29:55 +0100 Subject: [PATCH 13/59] Add export result by json format --- .../dataset/dataset-form.component.html | 4 ++++ .../dataset/dataset-form.component.ts | 4 ++++ .../dataset-by-family.component.spec.ts | 2 ++ .../dataset-card-doc.component.spec.ts | 1 + .../components/result/download.component.html | 4 ++++ .../result/download.component.spec.ts | 6 ++++++ .../result/reminder.component.spec.ts | 4 ++++ .../result/url-display.component.spec.ts | 4 ++++ .../components/summary.component.spec.ts | 2 ++ .../effects/search-multiple.effects.spec.ts | 1 + .../src/app/metamodel/models/dataset.model.ts | 1 + client/src/test-data.ts | 3 +++ conf-dev/create-db.sh | 10 +++++----- server/src/Action/DatasetAction.php | 1 + server/src/Action/DatasetListAction.php | 1 + .../src/Action/StartTaskCreateResultAction.php | 6 ++++-- server/src/Entity/Dataset.php | 18 ++++++++++++++++++ server/tests/Action/DatasetActionTest.php | 1 + server/tests/Action/DatasetListActionTest.php | 1 + 19 files changed, 67 insertions(+), 7 deletions(-) diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html index 6c2a8cf..7434b27 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html @@ -109,6 +109,10 @@
+
+ + +
diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts index 3e7080a..fa37612 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts @@ -53,6 +53,7 @@ export class DatasetFormComponent implements OnInit, OnChanges { cone_search_plot_enabled: new FormControl(false), download_enabled: new FormControl(true), download_opened: new FormControl(false), + download_json: new FormControl(true), download_csv: new FormControl(true), download_ascii: new FormControl(true), download_vo: new FormControl(false), @@ -155,6 +156,7 @@ export class DatasetFormComponent implements OnInit, OnChanges { checkDownloadDisableOpened() { if (this.form.controls.download_enabled.value) { this.form.controls.download_opened.enable(); + this.form.controls.download_json.enable(); this.form.controls.download_csv.enable(); this.form.controls.download_ascii.enable(); this.form.controls.download_vo.enable(); @@ -162,6 +164,8 @@ export class DatasetFormComponent implements OnInit, OnChanges { } else { this.form.controls.download_opened.setValue(false); this.form.controls.download_opened.disable(); + this.form.controls.download_json.setValue(false); + this.form.controls.download_json.disable(); this.form.controls.download_csv.setValue(false); this.form.controls.download_csv.disable(); this.form.controls.download_ascii.setValue(false); diff --git a/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts b/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts index 3878a0c..7479e1d 100644 --- a/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts +++ b/client/src/app/instance/documentation/components/dataset-by-family.component.spec.ts @@ -28,6 +28,7 @@ const DATASET_LIST: Dataset[] = [ cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -60,6 +61,7 @@ const DATASET_LIST: Dataset[] = [ cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, diff --git a/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts b/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts index 43226ca..2f4958f 100644 --- a/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts +++ b/client/src/app/instance/documentation/components/dataset-card-doc.component.spec.ts @@ -35,6 +35,7 @@ const DATASET: Dataset = { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, diff --git a/client/src/app/instance/search/components/result/download.component.html b/client/src/app/instance/search/components/result/download.component.html index cf29bc5..b4b0570 100644 --- a/client/src/app/instance/search/components/result/download.component.html +++ b/client/src/app/instance/search/components/result/download.component.html @@ -18,6 +18,10 @@

Download results just here:

+ + JSON + +   CSV diff --git a/client/src/app/instance/search/components/result/download.component.spec.ts b/client/src/app/instance/search/components/result/download.component.spec.ts index a98834b..c6a1b42 100644 --- a/client/src/app/instance/search/components/result/download.component.spec.ts +++ b/client/src/app/instance/search/components/result/download.component.spec.ts @@ -52,6 +52,7 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -84,6 +85,7 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_plot_enabled: false, download_enabled: false, download_opened: false, + download_json: true, download_csv: false, download_ascii: false, download_vo: false, @@ -125,6 +127,7 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -157,6 +160,7 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_plot_enabled: false, download_enabled: false, download_opened: false, + download_json: true, download_csv: false, download_ascii: false, download_vo: false, @@ -198,6 +202,7 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: false, download_vo: true, @@ -230,6 +235,7 @@ describe('[Instance][Search][Component][Result] DownloadComponent', () => { cone_search_plot_enabled: false, download_enabled: false, download_opened: false, + download_json: true, download_csv: false, download_ascii: false, download_vo: false, diff --git a/client/src/app/instance/search/components/result/reminder.component.spec.ts b/client/src/app/instance/search/components/result/reminder.component.spec.ts index 6e648e0..6f373fc 100644 --- a/client/src/app/instance/search/components/result/reminder.component.spec.ts +++ b/client/src/app/instance/search/components/result/reminder.component.spec.ts @@ -51,6 +51,7 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -83,6 +84,7 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -124,6 +126,7 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -156,6 +159,7 @@ describe('[Instance][Search][Component][Result] ReminderComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, diff --git a/client/src/app/instance/search/components/result/url-display.component.spec.ts b/client/src/app/instance/search/components/result/url-display.component.spec.ts index e09ba2f..eb4df7d 100644 --- a/client/src/app/instance/search/components/result/url-display.component.spec.ts +++ b/client/src/app/instance/search/components/result/url-display.component.spec.ts @@ -55,6 +55,7 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -87,6 +88,7 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -128,6 +130,7 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -160,6 +163,7 @@ describe('[Instance][Search][Component][Result] UrlDisplayComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, diff --git a/client/src/app/instance/search/components/summary.component.spec.ts b/client/src/app/instance/search/components/summary.component.spec.ts index 1a49dfa..f01e4aa 100644 --- a/client/src/app/instance/search/components/summary.component.spec.ts +++ b/client/src/app/instance/search/components/summary.component.spec.ts @@ -51,6 +51,7 @@ describe('[Instance][Search][Component] SummaryComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -83,6 +84,7 @@ describe('[Instance][Search][Component] SummaryComponent', () => { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, diff --git a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts index 88b5adb..f593f26 100644 --- a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts +++ b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts @@ -256,6 +256,7 @@ describe('[Instance][Store] SearchMultipleEffects', () => { cone_search_plot_opened: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, diff --git a/client/src/app/metamodel/models/dataset.model.ts b/client/src/app/metamodel/models/dataset.model.ts index eb041a8..fe4804d 100644 --- a/client/src/app/metamodel/models/dataset.model.ts +++ b/client/src/app/metamodel/models/dataset.model.ts @@ -29,6 +29,7 @@ export interface Dataset { cone_search_plot_enabled: boolean; download_enabled: boolean; download_opened: boolean; + download_json: boolean; download_csv: boolean; download_ascii: boolean; download_vo: boolean; diff --git a/client/src/test-data.ts b/client/src/test-data.ts index 1f98485..bd9375f 100644 --- a/client/src/test-data.ts +++ b/client/src/test-data.ts @@ -208,6 +208,7 @@ export const DATASET_LIST: Dataset[] = [ cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -240,6 +241,7 @@ export const DATASET_LIST: Dataset[] = [ cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, @@ -274,6 +276,7 @@ export const DATASET: Dataset = { cone_search_plot_enabled: false, download_enabled: true, download_opened: true, + download_json: true, download_csv: true, download_ascii: true, download_vo: true, diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh index 8fb223c..fed4add 100644 --- a/conf-dev/create-db.sh +++ b/conf-dev/create-db.sh @@ -73,11 +73,11 @@ curl -d '{"label":"SVOM dataset family","display":20,"opened":true}' --header 'C curl -d '{"label":"IRiS dataset family","display":30,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/instance/default/dataset-family # Add datasets -curl -d '{"name":"vipers_dr2_w1","table_ref":"aspic_vipers_dr2_w1","label":"VIPERS-W1 (DR2)","description":"VIPERS W1 dataset","display":10,"data_path":"\/ASPIC\/VIPERS_DR2","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"anis_survey"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/1/dataset -curl -d '{"name":"sp_cards","table_ref":"sp_cards","label":"SP Metadata","description":"Contains metadata of scientific products (Core Program & General Program)","display":30,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"svom"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/2/dataset -curl -d '{"name":"observations","table_ref":"v_observation","label":"IRiS obs","description":"IRiS observations","display":10,"data_path":"\/IRIS\/observations","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"iris"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/3/dataset -curl -d '{"name":"vvds_f02_udeep","table_ref":"aspic_vvds_f02_udeep","label":"VVDS2h Ultra Deep","description":"VVDS2h Ultra Deep","display":20,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"anis_survey"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/1/dataset -curl -d '{"name":"products","table_ref":"products","label":"Scientific Products","description":"SR3 & SR4 products list","display":20,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"svom"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/2/dataset +curl -d '{"name":"vipers_dr2_w1","table_ref":"aspic_vipers_dr2_w1","label":"VIPERS-W1 (DR2)","description":"VIPERS W1 dataset","display":10,"data_path":"\/ASPIC\/VIPERS_DR2","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_json":true,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"anis_survey"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/1/dataset +curl -d '{"name":"sp_cards","table_ref":"sp_cards","label":"SP Metadata","description":"Contains metadata of scientific products (Core Program & General Program)","display":30,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_json":true,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"svom"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/2/dataset +curl -d '{"name":"observations","table_ref":"v_observation","label":"IRiS obs","description":"IRiS observations","display":10,"data_path":"\/IRIS\/observations","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_json":true,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"iris"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/3/dataset +curl -d '{"name":"vvds_f02_udeep","table_ref":"aspic_vvds_f02_udeep","label":"VVDS2h Ultra Deep","description":"VVDS2h Ultra Deep","display":20,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_json":true,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"anis_survey"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/1/dataset +curl -d '{"name":"products","table_ref":"products","label":"Scientific Products","description":"SR3 & SR4 products list","display":20,"data_path":"","public":true,"info_survey_enabled":true,"info_survey_label":"More about this survey","cone_search_enabled":false,"cone_search_opened":true,"cone_search_column_ra":null,"cone_search_column_dec":null,"cone_search_plot_enabled":false,"download_enabled":true,"download_opened":false,"download_json":true,"download_csv":true,"download_ascii":true,"download_vo":false,"download_archive":true,"summary_enabled":true,"summary_opened":false,"server_link_enabled":false,"server_link_opened":false,"datatable_enabled":true,"datatable_opened":false,"datatable_selectable_rows":false,"survey_name":"svom"}' --header 'Content-Type: application/json' -X POST http://localhost/dataset-family/2/dataset # Add vipers_dr2_w1 attributes curl -d '{"label":"Default","display":10,"opened":true}' --header 'Content-Type: application/json' -X POST http://localhost/dataset/vipers_dr2_w1/criteria-family diff --git a/server/src/Action/DatasetAction.php b/server/src/Action/DatasetAction.php index e4cb81c..54ad950 100644 --- a/server/src/Action/DatasetAction.php +++ b/server/src/Action/DatasetAction.php @@ -130,6 +130,7 @@ final class DatasetAction extends AbstractAction $dataset->setConeSearchPlotEnabled($parsedBody['cone_search_plot_enabled']); $dataset->setDownloadEnabled($parsedBody['download_enabled']); $dataset->setDownloadOpened($parsedBody['download_opened']); + $dataset->setDownloadJson($parsedBody['download_json']); $dataset->setDownloadCsv($parsedBody['download_csv']); $dataset->setDownloadAscii($parsedBody['download_ascii']); $dataset->setDownloadVo($parsedBody['download_vo']); diff --git a/server/src/Action/DatasetListAction.php b/server/src/Action/DatasetListAction.php index 9f73779..1d84aad 100644 --- a/server/src/Action/DatasetListAction.php +++ b/server/src/Action/DatasetListAction.php @@ -134,6 +134,7 @@ final class DatasetListAction extends AbstractAction $dataset->setConeSearchPlotEnabled($parsedBody['cone_search_plot_enabled']); $dataset->setDownloadEnabled($parsedBody['download_enabled']); $dataset->setDownloadOpened($parsedBody['download_opened']); + $dataset->setDownloadJson($parsedBody['download_json']); $dataset->setDownloadCsv($parsedBody['download_csv']); $dataset->setDownloadAscii($parsedBody['download_ascii']); $dataset->setDownloadVo($parsedBody['download_vo']); diff --git a/server/src/Action/StartTaskCreateResultAction.php b/server/src/Action/StartTaskCreateResultAction.php index fc1e34e..ebe4284 100644 --- a/server/src/Action/StartTaskCreateResultAction.php +++ b/server/src/Action/StartTaskCreateResultAction.php @@ -109,7 +109,7 @@ final class StartTaskCreateResultAction extends AbstractAction ); $token = $request->getHeader('Authorization')[0]; } - + $queryParams = $request->getQueryParams(); // The parameter "a" is mandatory @@ -121,7 +121,9 @@ final class StartTaskCreateResultAction extends AbstractAction } // Search extension - if ($queryParams['f'] === 'csv') { + if ($queryParams['f'] === 'json') { + $extension = '.json'; + } elseif ($queryParams['f'] === 'csv') { $extension = '.csv'; } elseif ($queryParams['f'] === 'ascii') { $extension = '.txt'; diff --git a/server/src/Entity/Dataset.php b/server/src/Entity/Dataset.php index 3c78fdd..d425e5b 100644 --- a/server/src/Entity/Dataset.php +++ b/server/src/Entity/Dataset.php @@ -136,6 +136,13 @@ class Dataset implements \JsonSerializable */ protected $downloadOpened; + /** + * @var bool + * + * @Column(type="boolean", name="download_json", nullable=false) + */ + protected $downloadJson; + /** * @var bool * @@ -402,6 +409,16 @@ class Dataset implements \JsonSerializable $this->downloadOpened = $downloadOpened; } + public function getDownloadJson() + { + return $this->downloadJson; + } + + public function setDownloadJson($downloadJson) + { + $this->downloadJson = $downloadJson; + } + public function getDownloadCsv() { return $this->downloadCsv; @@ -556,6 +573,7 @@ class Dataset implements \JsonSerializable 'cone_search_plot_enabled' => $this->getConeSearchPlotEnabled(), 'download_enabled' => $this->getDownloadEnabled(), 'download_opened' => $this->getDownloadOpened(), + 'download_json' => $this->getDownloadJson(), 'download_csv' => $this->getDownloadCsv(), 'download_ascii' => $this->getDownloadAscii(), 'download_vo' => $this->getDownloadVo(), diff --git a/server/tests/Action/DatasetActionTest.php b/server/tests/Action/DatasetActionTest.php index b33db34..be0328d 100644 --- a/server/tests/Action/DatasetActionTest.php +++ b/server/tests/Action/DatasetActionTest.php @@ -136,6 +136,7 @@ final class DatasetActionTest extends TestCase 'cone_search_plot_enabled' => false, 'download_enabled' => true, 'download_opened' => false, + 'download_json' => true, 'download_csv' => true, 'download_ascii' => true, 'download_vo' => false, diff --git a/server/tests/Action/DatasetListActionTest.php b/server/tests/Action/DatasetListActionTest.php index d6e843b..c37563b 100644 --- a/server/tests/Action/DatasetListActionTest.php +++ b/server/tests/Action/DatasetListActionTest.php @@ -132,6 +132,7 @@ final class DatasetListActionTest extends TestCase 'cone_search_plot_enabled' => false, 'download_enabled' => true, 'download_opened' => false, + 'download_json' => true, 'download_csv' => true, 'download_ascii' => true, 'download_vo' => false, -- GitLab From 9999353f56d249ac78aa68140eabb3f8870a3bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= Date: Mon, 28 Mar 2022 17:18:35 +0200 Subject: [PATCH 14/59] Fixed bug admin select file --- .../data-path-form-control.component.html | 6 +- .../data-path-form-control.component.ts | 6 +- .../file-select-form-control.component.html | 6 +- .../file-select-form-control.component.ts | 12 +-- .../components/instance-form.component.html | 38 +++++----- .../components/instance-form.component.ts | 10 ++- .../containers/edit-instance.component.html | 6 +- .../containers/edit-instance.component.ts | 18 ++--- .../containers/new-instance.component.html | 6 +- .../containers/new-instance.component.ts | 18 ++--- .../dataset/dataset-form.component.html | 6 +- .../containers/edit-dataset.component.ts | 12 +-- .../containers/new-dataset.component.ts | 12 +-- ...ry.actions.ts => file-explorer.actions.ts} | 5 +- .../effects/file-explorer.effects.ts | 64 ++++++++++++++++ client/src/app/metamodel/effects/index.ts | 6 +- .../effects/root-directory.effects.spec.ts | 62 --------------- .../effects/root-directory.effects.ts | 45 ----------- client/src/app/metamodel/metamodel.reducer.ts | 8 +- .../reducers/file-explorer.reducer.ts | 61 +++++++++++++++ .../reducers/root-directory.reducer.spec.ts | 63 --------------- .../reducers/root-directory.reducer.ts | 76 ------------------- .../selectors/file-explorer.selector.ts | 33 ++++++++ .../selectors/root-directory.selector.spec.ts | 39 ---------- .../selectors/root-directory.selector.ts | 48 ------------ ...ry.service.ts => file-explorer.service.ts} | 15 +++- client/src/app/metamodel/services/index.ts | 6 +- .../services/root-directory.service.spec.ts | 41 ---------- 28 files changed, 264 insertions(+), 464 deletions(-) rename client/src/app/metamodel/actions/{root-directory.actions.ts => file-explorer.actions.ts} (57%) create mode 100644 client/src/app/metamodel/effects/file-explorer.effects.ts delete mode 100644 client/src/app/metamodel/effects/root-directory.effects.spec.ts delete mode 100644 client/src/app/metamodel/effects/root-directory.effects.ts create mode 100644 client/src/app/metamodel/reducers/file-explorer.reducer.ts delete mode 100644 client/src/app/metamodel/reducers/root-directory.reducer.spec.ts delete mode 100644 client/src/app/metamodel/reducers/root-directory.reducer.ts create mode 100644 client/src/app/metamodel/selectors/file-explorer.selector.ts delete mode 100644 client/src/app/metamodel/selectors/root-directory.selector.spec.ts delete mode 100644 client/src/app/metamodel/selectors/root-directory.selector.ts rename client/src/app/metamodel/services/{root-directory.service.ts => file-explorer.service.ts} (67%) delete mode 100644 client/src/app/metamodel/services/root-directory.service.spec.ts diff --git a/client/src/app/admin/admin-shared/components/data-path-form-control.component.html b/client/src/app/admin/admin-shared/components/data-path-form-control.component.html index dfe6a5f..b0227d4 100644 --- a/client/src/app/admin/admin-shared/components/data-path-form-control.component.html +++ b/client/src/app/admin/admin-shared/components/data-path-form-control.component.html @@ -17,13 +17,13 @@
- +

{{ fileExplorerPath }}

-
+
@@ -34,7 +34,7 @@ - + + [class.pointer]="checkPointer(fileInfo)">
diff --git a/client/src/app/admin/admin-shared/components/data-path-form-control.component.ts b/client/src/app/admin/admin-shared/components/data-path-form-control.component.ts index 9ff36df..c1585ee 100644 --- a/client/src/app/admin/admin-shared/components/data-path-form-control.component.ts +++ b/client/src/app/admin/admin-shared/components/data-path-form-control.component.ts @@ -21,9 +21,9 @@ import { FileInfo } from 'src/app/metamodel/models'; }) export class DataPathFormControlComponent { @Input() form: FormGroup; - @Input() rootDirectory: FileInfo[]; - @Input() rootDirectoryIsLoading: boolean; - @Input() rootDirectoryIsLoaded: boolean; + @Input() files: FileInfo[]; + @Input() directoryIsLoading: boolean; + @Input() directoryIsLoaded: boolean; @Output() loadRootDirectory: EventEmitter = new EventEmitter(); modalRef: BsModalRef; diff --git a/client/src/app/admin/admin-shared/components/file-select-form-control.component.html b/client/src/app/admin/admin-shared/components/file-select-form-control.component.html index ef2e1e3..01180cd 100644 --- a/client/src/app/admin/admin-shared/components/file-select-form-control.component.html +++ b/client/src/app/admin/admin-shared/components/file-select-form-control.component.html @@ -17,13 +17,13 @@
- +

{{ fileExplorerPath }}

-
+
@@ -34,7 +34,7 @@ - diff --git a/client/src/app/admin/admin-shared/components/file-select-form-control.component.ts b/client/src/app/admin/admin-shared/components/file-select-form-control.component.ts index 4c0e5cc..2986f65 100644 --- a/client/src/app/admin/admin-shared/components/file-select-form-control.component.ts +++ b/client/src/app/admin/admin-shared/components/file-select-form-control.component.ts @@ -24,10 +24,10 @@ export class FileSelectFormControlComponent implements OnChanges { @Input() disabled: boolean = false; @Input() controlName: string; @Input() controlLabel: string; - @Input() rootDirectory: FileInfo[]; - @Input() rootDirectoryIsLoading: boolean; - @Input() rootDirectoryIsLoaded: boolean; - @Output() loadRootDirectory: EventEmitter = new EventEmitter(); + @Input() files: FileInfo[]; + @Input() directoryIsLoading: boolean; + @Input() directoryIsLoaded: boolean; + @Output() loadDirectory: EventEmitter = new EventEmitter(); modalRef: BsModalRef; fileExplorerPath = ''; @@ -52,7 +52,7 @@ export class FileSelectFormControlComponent implements OnChanges { this.fileExplorerPath = ''; } this.modalRef = this.modalService.show(template); - this.loadRootDirectory.emit(this.fileExplorerPath); + this.loadDirectory.emit(this.fileExplorerPath); } click(fileInfo: FileInfo): void { @@ -70,7 +70,7 @@ export class FileSelectFormControlComponent implements OnChanges { } else { this.fileExplorerPath += `/${fileInfo.name}`; } - this.loadRootDirectory.emit(this.fileExplorerPath); + this.loadDirectory.emit(this.fileExplorerPath); } buildFilePath(fileInfo: FileInfo) { diff --git a/client/src/app/admin/instance/components/instance-form.component.html b/client/src/app/admin/instance/components/instance-form.component.html index e756ad5..70e1d90 100644 --- a/client/src/app/admin/instance/components/instance-form.component.html +++ b/client/src/app/admin/instance/components/instance-form.component.html @@ -19,9 +19,9 @@
@@ -39,10 +39,10 @@ [disabled]="isDataPathEmpty()" [controlName]="'portal_logo'" [controlLabel]="'Portal logo'" - [rootDirectory]="rootDirectory" - [rootDirectoryIsLoading]="rootDirectoryIsLoading" - [rootDirectoryIsLoaded]="rootDirectoryIsLoaded" - (loadRootDirectory)="loadRootDirectory.emit($event)"> + [files]="files" + [directoryIsLoading]="directoryIsLoading" + [directoryIsLoaded]="directoryIsLoaded" + (loadDirectory)="onChangeFileSelect($event)">
@@ -69,20 +69,20 @@ [disabled]="isDataPathEmpty()" [controlName]="'design_logo'" [controlLabel]="'Logo'" - [rootDirectory]="rootDirectory" - [rootDirectoryIsLoading]="rootDirectoryIsLoading" - [rootDirectoryIsLoaded]="rootDirectoryIsLoaded" - (loadRootDirectory)="loadRootDirectory.emit($event)"> + [files]="files" + [directoryIsLoading]="directoryIsLoading" + [directoryIsLoaded]="directoryIsLoaded" + (loadDirectory)="onChangeFileSelect($event)"> + [files]="files" + [directoryIsLoading]="directoryIsLoading" + [directoryIsLoaded]="directoryIsLoaded" + (loadDirectory)="onChangeFileSelect($event)"> @@ -101,10 +101,10 @@ [form]="getHomeConfigFormGroup()" [controlName]="'home_component_logo'" [controlLabel]="'Logo'" - [rootDirectory]="rootDirectory" - [rootDirectoryIsLoading]="rootDirectoryIsLoading" - [rootDirectoryIsLoaded]="rootDirectoryIsLoaded" - (loadRootDirectory)="loadRootDirectory.emit($event)"> + [files]="files" + [directoryIsLoading]="directoryIsLoading" + [directoryIsLoaded]="directoryIsLoaded" + (loadDirectory)="onChangeFileSelect($event)">
diff --git a/client/src/app/admin/instance/components/instance-form.component.ts b/client/src/app/admin/instance/components/instance-form.component.ts index 407768e..4283785 100644 --- a/client/src/app/admin/instance/components/instance-form.component.ts +++ b/client/src/app/admin/instance/components/instance-form.component.ts @@ -18,9 +18,9 @@ import { Instance, FileInfo } from 'src/app/metamodel/models'; }) export class InstanceFormComponent implements OnInit { @Input() instance: Instance; - @Input() rootDirectory: FileInfo[]; - @Input() rootDirectoryIsLoading: boolean; - @Input() rootDirectoryIsLoaded: boolean; + @Input() files: FileInfo[]; + @Input() directoryIsLoading: boolean; + @Input() directoryIsLoaded: boolean; @Output() loadRootDirectory: EventEmitter = new EventEmitter(); @Output() onSubmit: EventEmitter = new EventEmitter(); @@ -74,6 +74,10 @@ the fast implementation of a project data exchange platform in a dedicated infor return this.form.controls.data_path.value == ''; } + onChangeFileSelect(path: string) { + this.loadRootDirectory.emit(`${this.form.controls.data_path.value}${path}`); + } + getHomeConfigFormGroup() { return this.form.controls.home_component_config as FormGroup; } diff --git a/client/src/app/admin/instance/containers/edit-instance.component.html b/client/src/app/admin/instance/containers/edit-instance.component.html index 1a8c574..419925a 100644 --- a/client/src/app/admin/instance/containers/edit-instance.component.html +++ b/client/src/app/admin/instance/containers/edit-instance.component.html @@ -12,9 +12,9 @@
diff --git a/client/src/app/admin/instance/containers/edit-instance.component.ts b/client/src/app/admin/instance/containers/edit-instance.component.ts index 278e28c..590a1e0 100644 --- a/client/src/app/admin/instance/containers/edit-instance.component.ts +++ b/client/src/app/admin/instance/containers/edit-instance.component.ts @@ -15,8 +15,8 @@ import { Observable } from 'rxjs'; import { Instance, FileInfo } from 'src/app/metamodel/models'; import * as instanceActions from 'src/app/metamodel/actions/instance.actions'; import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; -import * as rootDirectoryActions from 'src/app/metamodel/actions/root-directory.actions'; -import * as rootDirectorySelector from 'src/app/metamodel/selectors/root-directory.selector'; +import * as fileExplorerActions from 'src/app/metamodel/actions/file-explorer.actions'; +import * as fileExplorerSelector from 'src/app/metamodel/selectors/file-explorer.selector'; @Component({ selector: 'app-edit-instance', @@ -24,15 +24,15 @@ import * as rootDirectorySelector from 'src/app/metamodel/selectors/root-directo }) export class EditInstanceComponent { public instance: Observable; - public rootDirectory: Observable; - public rootDirectoryIsLoading: Observable; - public rootDirectoryIsLoaded: Observable; + public files: Observable; + public directoryIsLoading: Observable; + public directoryIsLoaded: Observable; constructor(private store: Store<{ }>) { this.instance = store.select(instanceSelector.selectInstanceByRouteName); - this.rootDirectory = store.select(rootDirectorySelector.selectAllFileInfo); - this.rootDirectoryIsLoading = store.select(rootDirectorySelector.selectRootDirectoryIsLoading); - this.rootDirectoryIsLoaded = store.select(rootDirectorySelector.selectRootDirectoryIsLoaded); + this.files = store.select(fileExplorerSelector.selectFiles); + this.directoryIsLoading = store.select(fileExplorerSelector.selectDirectoryIsLoading); + this.directoryIsLoaded = store.select(fileExplorerSelector.selectDirectoryIsLoaded); } editInstance(instance: Instance) { @@ -40,6 +40,6 @@ export class EditInstanceComponent { } loadRootDirectory(path: string) { - this.store.dispatch(rootDirectoryActions.loadRootDirectory({ path })); + this.store.dispatch(fileExplorerActions.loadRootDirectory({ path })); } } diff --git a/client/src/app/admin/instance/containers/new-instance.component.html b/client/src/app/admin/instance/containers/new-instance.component.html index 5566a51..ed3f205 100644 --- a/client/src/app/admin/instance/containers/new-instance.component.html +++ b/client/src/app/admin/instance/containers/new-instance.component.html @@ -11,9 +11,9 @@
diff --git a/client/src/app/admin/instance/containers/new-instance.component.ts b/client/src/app/admin/instance/containers/new-instance.component.ts index 9c3383c..70756b1 100644 --- a/client/src/app/admin/instance/containers/new-instance.component.ts +++ b/client/src/app/admin/instance/containers/new-instance.component.ts @@ -14,22 +14,22 @@ import { Observable } from 'rxjs'; import { Instance, FileInfo } from 'src/app/metamodel/models'; import * as instanceActions from 'src/app/metamodel/actions/instance.actions'; -import * as rootDirectoryActions from 'src/app/metamodel/actions/root-directory.actions'; -import * as rootDirectorySelector from 'src/app/metamodel/selectors/root-directory.selector'; +import * as fileExplorerActions from 'src/app/metamodel/actions/file-explorer.actions'; +import * as fileExplorerSelector from 'src/app/metamodel/selectors/file-explorer.selector'; @Component({ selector: 'app-new-instance', templateUrl: 'new-instance.component.html' }) export class NewInstanceComponent { - public rootDirectory: Observable; - public rootDirectoryIsLoading: Observable; - public rootDirectoryIsLoaded: Observable; + public files: Observable; + public directoryIsLoading: Observable; + public directoryIsLoaded: Observable; constructor(private store: Store<{ }>) { - this.rootDirectory = store.select(rootDirectorySelector.selectAllFileInfo); - this.rootDirectoryIsLoading = store.select(rootDirectorySelector.selectRootDirectoryIsLoading); - this.rootDirectoryIsLoaded = store.select(rootDirectorySelector.selectRootDirectoryIsLoaded); + this.files = store.select(fileExplorerSelector.selectFiles); + this.directoryIsLoading = store.select(fileExplorerSelector.selectDirectoryIsLoading); + this.directoryIsLoaded = store.select(fileExplorerSelector.selectDirectoryIsLoaded); } addNewInstance(instance: Instance) { @@ -37,6 +37,6 @@ export class NewInstanceComponent { } loadRootDirectory(path: string) { - this.store.dispatch(rootDirectoryActions.loadRootDirectory({ path })); + this.store.dispatch(fileExplorerActions.loadRootDirectory({ path })); } } diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html index 7434b27..5680185 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html @@ -42,9 +42,9 @@
diff --git a/client/src/app/admin/instance/dataset/containers/edit-dataset.component.ts b/client/src/app/admin/instance/dataset/containers/edit-dataset.component.ts index a816c37..1eaadb2 100644 --- a/client/src/app/admin/instance/dataset/containers/edit-dataset.component.ts +++ b/client/src/app/admin/instance/dataset/containers/edit-dataset.component.ts @@ -23,8 +23,8 @@ import * as tableActions from 'src/app/metamodel/actions/table.actions'; import * as tableSelector from 'src/app/metamodel/selectors/table.selector'; import * as datasetFamilySelector from 'src/app/metamodel/selectors/dataset-family.selector'; import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; -import * as rootDirectoryActions from 'src/app/metamodel/actions/root-directory.actions'; -import * as rootDirectorySelector from 'src/app/metamodel/selectors/root-directory.selector'; +import * as fileExplorerActions from 'src/app/metamodel/actions/file-explorer.actions'; +import * as fileExplorerSelector from 'src/app/metamodel/selectors/file-explorer.selector'; @Component({ selector: 'app-edit-dataset', @@ -70,9 +70,9 @@ export class EditDatasetComponent implements OnInit { this.datasetFamilyListIsLoading = store.select(datasetFamilySelector.selectDatasetFamilyListIsLoading); this.datasetFamilyListIsLoaded = store.select(datasetFamilySelector.selectDatasetFamilyListIsLoaded); this.datasetFamilyList = store.select(datasetFamilySelector.selectAllDatasetFamilies); - this.rootDirectory = store.select(rootDirectorySelector.selectAllFileInfo); - this.rootDirectoryIsLoading = store.select(rootDirectorySelector.selectRootDirectoryIsLoading); - this.rootDirectoryIsLoaded = store.select(rootDirectorySelector.selectRootDirectoryIsLoaded); + this.rootDirectory = store.select(fileExplorerSelector.selectFiles); + this.rootDirectoryIsLoading = store.select(fileExplorerSelector.selectDirectoryIsLoading); + this.rootDirectoryIsLoaded = store.select(fileExplorerSelector.selectDirectoryIsLoaded); } ngOnInit() { @@ -84,7 +84,7 @@ export class EditDatasetComponent implements OnInit { } loadRootDirectory(path: string) { - this.store.dispatch(rootDirectoryActions.loadRootDirectory({ path })); + this.store.dispatch(fileExplorerActions.loadRootDirectory({ path })); } editDataset(dataset: Dataset) { diff --git a/client/src/app/admin/instance/dataset/containers/new-dataset.component.ts b/client/src/app/admin/instance/dataset/containers/new-dataset.component.ts index a9de9b2..73393f6 100644 --- a/client/src/app/admin/instance/dataset/containers/new-dataset.component.ts +++ b/client/src/app/admin/instance/dataset/containers/new-dataset.component.ts @@ -21,8 +21,8 @@ import * as tableSelector from 'src/app/metamodel/selectors/table.selector'; import * as datasetFamilySelector from 'src/app/metamodel/selectors/dataset-family.selector'; import * as datasetActions from 'src/app/metamodel/actions/dataset.actions'; import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; -import * as rootDirectoryActions from 'src/app/metamodel/actions/root-directory.actions'; -import * as rootDirectorySelector from 'src/app/metamodel/selectors/root-directory.selector'; +import * as fileExplorerActions from 'src/app/metamodel/actions/file-explorer.actions'; +import * as fileExplorerSelector from 'src/app/metamodel/selectors/file-explorer.selector'; @Component({ selector: 'app-new-dataset', @@ -55,9 +55,9 @@ export class NewDatasetComponent implements OnInit { this.datasetFamilyListIsLoading = store.select(datasetFamilySelector.selectDatasetFamilyListIsLoading); this.datasetFamilyListIsLoaded = store.select(datasetFamilySelector.selectDatasetFamilyListIsLoaded); this.datasetFamilyList = store.select(datasetFamilySelector.selectAllDatasetFamilies); - this.rootDirectory = store.select(rootDirectorySelector.selectAllFileInfo); - this.rootDirectoryIsLoading = store.select(rootDirectorySelector.selectRootDirectoryIsLoading); - this.rootDirectoryIsLoaded = store.select(rootDirectorySelector.selectRootDirectoryIsLoaded); + this.rootDirectory = store.select(fileExplorerSelector.selectFiles); + this.rootDirectoryIsLoading = store.select(fileExplorerSelector.selectDirectoryIsLoading); + this.rootDirectoryIsLoaded = store.select(fileExplorerSelector.selectDirectoryIsLoaded); } ngOnInit() { @@ -71,7 +71,7 @@ export class NewDatasetComponent implements OnInit { } loadRootDirectory(path: string) { - this.store.dispatch(rootDirectoryActions.loadRootDirectory({ path })); + this.store.dispatch(fileExplorerActions.loadRootDirectory({ path })); } addNewDataset(dataset: Dataset) { diff --git a/client/src/app/metamodel/actions/root-directory.actions.ts b/client/src/app/metamodel/actions/file-explorer.actions.ts similarity index 57% rename from client/src/app/metamodel/actions/root-directory.actions.ts rename to client/src/app/metamodel/actions/file-explorer.actions.ts index e702eff..2eac1f0 100644 --- a/client/src/app/metamodel/actions/root-directory.actions.ts +++ b/client/src/app/metamodel/actions/file-explorer.actions.ts @@ -12,5 +12,6 @@ import { createAction, props } from '@ngrx/store'; import { FileInfo } from '../models'; export const loadRootDirectory = createAction('[Metamodel] Load Root Directory', props<{ path: string }>()); -export const loadRootDirectorySuccess = createAction('[Metamodel] Load Root Directory Success', props<{ files: FileInfo[] }>()); -export const loadRootDirectoryFail = createAction('[Metamodel] Load Root Directory Fail'); +export const loadDatasetDirectory = createAction('[Metamodel] Load Dataset Directory', props<{ path: string }>()); +export const loadDirectorySuccess = createAction('[Metamodel] Load Root Directory Success', props<{ files: FileInfo[] }>()); +export const loadDirectoryFail = createAction('[Metamodel] Load Root Directory Fail'); \ No newline at end of file diff --git a/client/src/app/metamodel/effects/file-explorer.effects.ts b/client/src/app/metamodel/effects/file-explorer.effects.ts new file mode 100644 index 0000000..cd99b62 --- /dev/null +++ b/client/src/app/metamodel/effects/file-explorer.effects.ts @@ -0,0 +1,64 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Injectable } from '@angular/core'; + +import { Actions, createEffect, ofType, concatLatestFrom } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { of } from 'rxjs'; +import { map, mergeMap, catchError } from 'rxjs/operators'; + +import * as fileExplorerActions from '../actions/file-explorer.actions'; +import { FileExplorerService } from '../services/file-explorer.service'; +import * as instanceSelector from '../selectors/instance.selector'; +import * as datasetSelector from '../selectors/dataset.selector'; + +/** + * @class + * @classdesc File explorer effects. + */ +@Injectable() +export class FileExplorerEffects { + /** + * Calls action to retrieve file list for the given root directory. + */ + loadRootDirectory$ = createEffect((): any => + this.actions$.pipe( + ofType(fileExplorerActions.loadRootDirectory), + mergeMap(action => this.fileExplorerService.retrieveRootDirectory(action.path) + .pipe( + map(files => fileExplorerActions.loadDirectorySuccess({ files })), + catchError(() => of(fileExplorerActions.loadDirectoryFail())) + ) + ) + ) + ); + + /** + * Calls action to retrieve file list for the given dataset directory. + */ + loadDatasetDirectory$ = createEffect((): any => + this.actions$.pipe( + ofType(fileExplorerActions.loadDatasetDirectory), + concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), + mergeMap(([action, datasetName]) => this.fileExplorerService.retrieveDatasetDirectory(datasetName, action.path) + .pipe( + map(files => fileExplorerActions.loadDirectorySuccess({ files })), + catchError(() => of(fileExplorerActions.loadDirectoryFail())) + ) + ) + ) + ); + + constructor( + private actions$: Actions, + private fileExplorerService: FileExplorerService, + private store: Store<{ }> + ) {} +} diff --git a/client/src/app/metamodel/effects/index.ts b/client/src/app/metamodel/effects/index.ts index 2be316f..e024d70 100644 --- a/client/src/app/metamodel/effects/index.ts +++ b/client/src/app/metamodel/effects/index.ts @@ -20,10 +20,10 @@ import { InstanceGroupEffects } from './instance-group.effects'; import { CriteriaFamilyEffects } from './criteria-family.effects'; import { OutputCategoryEffects } from './output-category.effects'; import { OutputFamilyEffects } from './output-family.effects'; -import { RootDirectoryEffects } from './root-directory.effects' import { SelectEffects } from './select.effects'; import { SelectOptionEffects } from './select-option.effects'; import { AttributeDistinctEffects } from './attribute-distinct.effects'; +import { FileExplorerEffects } from './file-explorer.effects'; export const metamodelEffects = [ DatabaseEffects, @@ -39,8 +39,8 @@ export const metamodelEffects = [ CriteriaFamilyEffects, OutputCategoryEffects, OutputFamilyEffects, - RootDirectoryEffects, SelectEffects, SelectOptionEffects, - AttributeDistinctEffects + AttributeDistinctEffects, + FileExplorerEffects ]; diff --git a/client/src/app/metamodel/effects/root-directory.effects.spec.ts b/client/src/app/metamodel/effects/root-directory.effects.spec.ts deleted file mode 100644 index c2704c8..0000000 --- a/client/src/app/metamodel/effects/root-directory.effects.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { provideMockActions } from '@ngrx/effects/testing'; -import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; -import { Observable } from 'rxjs'; -import { cold, hot } from 'jasmine-marbles'; - -import { RootDirectoryEffects } from './root-directory.effects'; -import { RootDirectoryService } from '../services/root-directory.service'; -import * as rootDirectoryActions from '../actions/root-directory.actions'; -import { FILES } from '../../../test-data'; - -describe('[Metamodel][Effects] RootDirectoryEffects', () => { - let actions = new Observable(); - let effects: RootDirectoryEffects; - let metadata: EffectsMetadata; - let service: RootDirectoryService; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - RootDirectoryEffects, - { provide: RootDirectoryService, useValue: { }}, - provideMockActions(() => actions) - ] - }).compileComponents(); - effects = TestBed.inject(RootDirectoryEffects); - metadata = getEffectsMetadata(effects); - service = TestBed.inject(RootDirectoryService); - }); - - it('should be created', () => { - expect(effects).toBeTruthy(); - }); - - describe('loadRootDirectory$ effect', () => { - it('should dispatch the loadRootDirectorySuccess action on success', () => { - const action = rootDirectoryActions.loadRootDirectory({ path: 'path' }); - const outcome = rootDirectoryActions.loadRootDirectorySuccess({ files: FILES }); - - actions = hot('-a', { a: action }); - const response = cold('-a|', { a: FILES }); - const expected = cold('--b', { b: outcome }); - service.retrieveRootDirectory = jest.fn(() => response); - - expect(effects.loadRootDirectory$).toBeObservable(expected); - }); - - it('should dispatch the loadRootDirectoryFail action on HTTP failure', () => { - const action = rootDirectoryActions.loadRootDirectory({ path: 'path' }); - const error = new Error(); - const outcome = rootDirectoryActions.loadRootDirectoryFail(); - - actions = hot('-a', { a: action }); - const response = cold('-#|', { }, error); - const expected = cold('--b', { b: outcome }); - service.retrieveRootDirectory = jest.fn(() => response); - - expect(effects.loadRootDirectory$).toBeObservable(expected); - }); - }); -}); diff --git a/client/src/app/metamodel/effects/root-directory.effects.ts b/client/src/app/metamodel/effects/root-directory.effects.ts deleted file mode 100644 index f8ff1ff..0000000 --- a/client/src/app/metamodel/effects/root-directory.effects.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * This file is part of Anis Client. - * - * @copyright Laboratoire d'Astrophysique de Marseille / CNRS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Injectable } from '@angular/core'; - -import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { of } from 'rxjs'; -import { map, mergeMap, catchError } from 'rxjs/operators'; - -import * as rootDirectoryActions from '../actions/root-directory.actions'; -import { RootDirectoryService } from '../services/root-directory.service'; - -/** - * @class - * @classdesc Root directory effects. - */ -@Injectable() -export class RootDirectoryEffects { - - /** - * Calls action to retrieve file list for the given root directory. - */ - loadRootDirectory$ = createEffect((): any => - this.actions$.pipe( - ofType(rootDirectoryActions.loadRootDirectory), - mergeMap(action => this.rootDirectoryService.retrieveRootDirectory(action.path) - .pipe( - map(files => rootDirectoryActions.loadRootDirectorySuccess({ files })), - catchError(() => of(rootDirectoryActions.loadRootDirectoryFail())) - ) - ) - ) - ); - - constructor( - private actions$: Actions, - private rootDirectoryService: RootDirectoryService - ) {} -} diff --git a/client/src/app/metamodel/metamodel.reducer.ts b/client/src/app/metamodel/metamodel.reducer.ts index 2873d83..a58d938 100644 --- a/client/src/app/metamodel/metamodel.reducer.ts +++ b/client/src/app/metamodel/metamodel.reducer.ts @@ -23,10 +23,10 @@ import * as attribute from './reducers/attribute.reducer'; import * as criteriaFamily from './reducers/criteria-family.reducer'; import * as outputCategory from './reducers/output-category.reducer'; import * as outputFamily from './reducers/output-family.reducer'; -import * as rootDirectory from './reducers/root-directory.reducer'; import * as select from './reducers/select.reducer'; import * as selectOption from './reducers/select-option.reducer'; import * as attributeDistinct from './reducers/attribute-distinct.reducer'; +import * as fileExplorer from './reducers/file-explorer.reducer'; /** * Interface for metamodel state. @@ -47,10 +47,10 @@ export interface State { criteriaFamily: criteriaFamily.State; outputCategory: outputCategory.State; outputFamily: outputFamily.State; - rootDirectory: rootDirectory.State; select: select.State; selectOption: selectOption.State; attributeDistinct: attributeDistinct.State; + fileExplorer: fileExplorer.State; } const reducers = { @@ -67,10 +67,10 @@ const reducers = { criteriaFamily: criteriaFamily.criteriaFamilyReducer, outputCategory: outputCategory.outputCategoryReducer, outputFamily: outputFamily.outputFamilyReducer, - rootDirectory: rootDirectory.rootDirectoryReducer, select: select.selectReducer, selectOption: selectOption.selectOptionReducer, - attributeDistinct: attributeDistinct.attributeDistinctReducer + attributeDistinct: attributeDistinct.attributeDistinctReducer, + fileExplorer: fileExplorer.fileExplorerReducer }; export const metamodelReducer = combineReducers(reducers); diff --git a/client/src/app/metamodel/reducers/file-explorer.reducer.ts b/client/src/app/metamodel/reducers/file-explorer.reducer.ts new file mode 100644 index 0000000..c625f43 --- /dev/null +++ b/client/src/app/metamodel/reducers/file-explorer.reducer.ts @@ -0,0 +1,61 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createReducer, on } from '@ngrx/store'; + +import * as fileExplorerActions from '../actions/file-explorer.actions'; +import { FileInfo } from '../models'; + +/** + * Interface for root directory state. + * + * @interface State + */ +export interface State { + files: FileInfo[]; + directoryIsLoading: boolean; + directoryIsLoaded: boolean; +} + +export const initialState: State = { + files: null, + directoryIsLoading: false, + directoryIsLoaded: false +}; + +export const fileExplorerReducer = createReducer( + initialState, + on(fileExplorerActions.loadRootDirectory, (state) => ({ + ...state, + files: null, + directoryIsLoading: true, + directoryIsLoaded: false + })), + on(fileExplorerActions.loadDatasetDirectory, (state) => ({ + ...state, + files: null, + directoryIsLoading: true, + directoryIsLoaded: false + })), + on(fileExplorerActions.loadDirectorySuccess, (state, { files }) => ({ + ...state, + files, + directoryIsLoading: false, + directoryIsLoaded: true + })), + on(fileExplorerActions.loadDirectoryFail, state => ({ + ...state, + directoryIsLoading: false, + directoryIsLoaded: false + })) +); + +export const selectFiles = (state: State) => state.files; +export const selectDirectoryIsLoading = (state: State) => state.directoryIsLoading; +export const selectDirectoryIsLoaded = (state: State) => state.directoryIsLoaded; diff --git a/client/src/app/metamodel/reducers/root-directory.reducer.spec.ts b/client/src/app/metamodel/reducers/root-directory.reducer.spec.ts deleted file mode 100644 index 942dcd4..0000000 --- a/client/src/app/metamodel/reducers/root-directory.reducer.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Action } from '@ngrx/store'; - -import * as fromRootDirectory from './root-directory.reducer'; -import * as rootDirectoryActions from '../actions/root-directory.actions'; -import { FILES } from '../../../test-data'; - -describe('[Metamodel][Reducers] RootDirectory reducer', () => { - it('unknown action should return the default state', () => { - const { initialState } = fromRootDirectory; - const action = { type: 'Unknown' }; - const state = fromRootDirectory.rootDirectoryReducer(initialState, action); - expect(state).toBe(initialState); - }); - - it('loadRootDirectory action should set rootDirectoryIsLoading to true', () => { - const { initialState } = fromRootDirectory; - const action = rootDirectoryActions.loadRootDirectory({ path: 'path' }); - const state = fromRootDirectory.rootDirectoryReducer(initialState, action); - expect(state.ids.length).toEqual(0); - expect(state.entities).toEqual({ }); - expect(state.rootDirectoryIsLoading).toEqual(true); - expect(state.rootDirectoryIsLoaded).toEqual(false); - expect(state).not.toBe(initialState); - }); - - it('loadRootDirectorySuccess action should add files, set rootDirectoryIsLoading to false and set rootDirectoryIsLoaded to true', () => { - const { initialState } = fromRootDirectory; - const action = rootDirectoryActions.loadRootDirectorySuccess({ files: FILES }); - const state = fromRootDirectory.rootDirectoryReducer(initialState, action); - expect(state.ids.length).toEqual(2); - expect(state.ids).toContain('file-one'); - expect(state.ids).toContain('file-two'); - expect(Object.keys(state.entities).length).toEqual(2); - expect(state.rootDirectoryIsLoading).toEqual(false); - expect(state.rootDirectoryIsLoaded).toEqual(true); - expect(state).not.toBe(initialState); - }); - - it('loadRootDirectoryFail action should set rootDirectoryIsLoading to false', () => { - const { initialState } = fromRootDirectory; - const action = rootDirectoryActions.loadRootDirectoryFail(); - const state = fromRootDirectory.rootDirectoryReducer(initialState, action); - expect(state.ids.length).toEqual(0); - expect(state.entities).toEqual({ }); - expect(state.rootDirectoryIsLoading).toEqual(false); - expect(state.rootDirectoryIsLoaded).toEqual(false); - expect(state).not.toBe(initialState); - }); - - it('should get rootDirectoryIsLoading', () => { - const action = {} as Action; - const state = fromRootDirectory.rootDirectoryReducer(undefined, action); - - expect(fromRootDirectory.selectRootDirectoryIsLoading(state)).toEqual(false); - }); - - it('should get rootDirectoryIsLoaded', () => { - const action = {} as Action; - const state = fromRootDirectory.rootDirectoryReducer(undefined, action); - - expect(fromRootDirectory.selectRootDirectoryIsLoaded(state)).toEqual(false); - }); -}); diff --git a/client/src/app/metamodel/reducers/root-directory.reducer.ts b/client/src/app/metamodel/reducers/root-directory.reducer.ts deleted file mode 100644 index efab0b9..0000000 --- a/client/src/app/metamodel/reducers/root-directory.reducer.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * This file is part of Anis Client. - * - * @copyright Laboratoire d'Astrophysique de Marseille / CNRS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { createReducer, on } from '@ngrx/store'; -import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; - -import * as rootDirectoryActions from '../actions/root-directory.actions'; -import { FileInfo } from '../models'; - -/** - * Interface for root directory state. - * - * @interface State - */ -export interface State extends EntityState { - rootDirectoryIsLoading: boolean; - rootDirectoryIsLoaded: boolean; -} - -export const adapter: EntityAdapter = createEntityAdapter({ - selectId: (fileInfo: FileInfo) => fileInfo.name, - sortComparer: (a: FileInfo, b: FileInfo) => a.name.localeCompare(b.name) -}); - -export const initialState: State = adapter.getInitialState({ - rootDirectoryIsLoading: false, - rootDirectoryIsLoaded: false -}); - -export const rootDirectoryReducer = createReducer( - initialState, - on(rootDirectoryActions.loadRootDirectory, (state) => { - return { - ...state, - rootDirectoryIsLoading: true, - rootDirectoryIsLoaded: false - } - }), - on(rootDirectoryActions.loadRootDirectorySuccess, (state, { files }) => { - return adapter.setAll( - files, - { - ...state, - rootDirectoryIsLoading: false, - rootDirectoryIsLoaded: true - } - ); - }), - on(rootDirectoryActions.loadRootDirectoryFail, (state) => { - return { - ...state, - rootDirectoryIsLoading: false - } - }) -); - -const { - selectIds, - selectEntities, - selectAll, - selectTotal, -} = adapter.getSelectors(); - -export const selectFileInfoIds = selectIds; -export const selectFileInfoEntities = selectEntities; -export const selectAllFileInfo = selectAll; -export const selectFileInfoTotal = selectTotal; - -export const selectRootDirectoryIsLoading = (state: State) => state.rootDirectoryIsLoading; -export const selectRootDirectoryIsLoaded = (state: State) => state.rootDirectoryIsLoaded; diff --git a/client/src/app/metamodel/selectors/file-explorer.selector.ts b/client/src/app/metamodel/selectors/file-explorer.selector.ts new file mode 100644 index 0000000..a7d2b30 --- /dev/null +++ b/client/src/app/metamodel/selectors/file-explorer.selector.ts @@ -0,0 +1,33 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { createSelector } from '@ngrx/store'; + +import * as reducer from '../metamodel.reducer'; +import * as fromFileExplorer from '../reducers/file-explorer.reducer'; + +export const selectFileExplorerState = createSelector( + reducer.getMetamodelState, + (state: reducer.State) => state.fileExplorer +); + +export const selectFiles = createSelector( + selectFileExplorerState, + fromFileExplorer.selectFiles +); + +export const selectDirectoryIsLoading = createSelector( + selectFileExplorerState, + fromFileExplorer.selectDirectoryIsLoading +); + +export const selectDirectoryIsLoaded = createSelector( + selectFileExplorerState, + fromFileExplorer.selectDirectoryIsLoaded +); diff --git a/client/src/app/metamodel/selectors/root-directory.selector.spec.ts b/client/src/app/metamodel/selectors/root-directory.selector.spec.ts deleted file mode 100644 index ab145eb..0000000 --- a/client/src/app/metamodel/selectors/root-directory.selector.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as rootDirectorySelector from './root-directory.selector'; -import * as fromRootDirectory from '../reducers/root-directory.reducer'; - -describe('[Metamodel][Selector] Root Directory selector', () => { - it('should get rootDirectory state', () => { - const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}}; - expect(rootDirectorySelector.selectRootDirectoryState(state)).toEqual(state.metamodel.rootDirectory); - }); - - it('should get file info IDs', () => { - const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}}; - expect(rootDirectorySelector.selectFileInfoIds(state).length).toEqual(0); - }); - - it('should get file info entities', () => { - const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}}; - expect(rootDirectorySelector.selectFileInfoEntities(state)).toEqual({ }); - }); - - it('should get all file info', () => { - const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}}; - expect(rootDirectorySelector.selectAllFileInfo(state).length).toEqual(0); - }); - - it('should get file info count', () => { - const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}}; - expect(rootDirectorySelector.selectFileInfoTotal(state)).toEqual(0); - }); - - it('should get rootDirectoryIsLoading', () => { - const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}}; - expect(rootDirectorySelector.selectRootDirectoryIsLoading(state)).toBe(false); - }); - - it('should get rootDirectoryIsLoading', () => { - const state = { metamodel: { rootDirectory: { ...fromRootDirectory.initialState }}}; - expect(rootDirectorySelector.selectRootDirectoryIsLoaded(state)).toBe(false); - }); -}); diff --git a/client/src/app/metamodel/selectors/root-directory.selector.ts b/client/src/app/metamodel/selectors/root-directory.selector.ts deleted file mode 100644 index 8462cfc..0000000 --- a/client/src/app/metamodel/selectors/root-directory.selector.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * This file is part of Anis Client. - * - * @copyright Laboratoire d'Astrophysique de Marseille / CNRS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { createSelector } from '@ngrx/store'; - -import * as reducer from '../metamodel.reducer'; -import * as fromRootDirectory from '../reducers/root-directory.reducer'; - -export const selectRootDirectoryState = createSelector( - reducer.getMetamodelState, - (state: reducer.State) => state.rootDirectory -); - -export const selectFileInfoIds = createSelector( - selectRootDirectoryState, - fromRootDirectory.selectFileInfoIds -); - -export const selectFileInfoEntities = createSelector( - selectRootDirectoryState, - fromRootDirectory.selectFileInfoEntities -); - -export const selectAllFileInfo = createSelector( - selectRootDirectoryState, - fromRootDirectory.selectAllFileInfo -); - -export const selectFileInfoTotal = createSelector( - selectRootDirectoryState, - fromRootDirectory.selectFileInfoTotal -); - -export const selectRootDirectoryIsLoading = createSelector( - selectRootDirectoryState, - fromRootDirectory.selectRootDirectoryIsLoading -); - -export const selectRootDirectoryIsLoaded = createSelector( - selectRootDirectoryState, - fromRootDirectory.selectRootDirectoryIsLoaded -); diff --git a/client/src/app/metamodel/services/root-directory.service.ts b/client/src/app/metamodel/services/file-explorer.service.ts similarity index 67% rename from client/src/app/metamodel/services/root-directory.service.ts rename to client/src/app/metamodel/services/file-explorer.service.ts index ed8c19c..4894cd4 100644 --- a/client/src/app/metamodel/services/root-directory.service.ts +++ b/client/src/app/metamodel/services/file-explorer.service.ts @@ -17,10 +17,10 @@ import { AppConfigService } from 'src/app/app-config.service'; /** * @class - * @classdesc Root directory service. + * @classdesc File explorer service. */ @Injectable() -export class RootDirectoryService { +export class FileExplorerService { constructor(private http: HttpClient, private config: AppConfigService) { } /** @@ -33,4 +33,15 @@ export class RootDirectoryService { retrieveRootDirectory(path: string): Observable { return this.http.get(`${this.config.apiUrl}/file-explorer${path}`); } + + /** + * Retrieves dataset directory with the given path. + * + * @param {string} path - The path. + * + * @return Observable + */ + retrieveDatasetDirectory(datasetName: string, path: string): Observable { + return this.http.get(`${this.config.apiUrl}//dataset-file-explorer//${datasetName}${path}`); + } } diff --git a/client/src/app/metamodel/services/index.ts b/client/src/app/metamodel/services/index.ts index 414036d..5433cee 100644 --- a/client/src/app/metamodel/services/index.ts +++ b/client/src/app/metamodel/services/index.ts @@ -20,10 +20,10 @@ import { AttributeService } from './attribute.service'; import { CriteriaFamilyService } from './criteria-family.service'; import { OutputCategoryService } from './output-category.service'; import { OutputFamilyService } from './output-family.service'; -import { RootDirectoryService } from './root-directory.service'; import { SelectService } from './select.service'; import { SelectOptionService } from './select-option.service'; import { AttributeDistinctService } from './attribute-distinct.service'; +import { FileExplorerService } from './file-explorer.service'; export const metamodelServices = [ DatabaseService, @@ -39,8 +39,8 @@ export const metamodelServices = [ CriteriaFamilyService, OutputCategoryService, OutputFamilyService, - RootDirectoryService, SelectService, SelectOptionService, - AttributeDistinctService + AttributeDistinctService, + FileExplorerService ]; diff --git a/client/src/app/metamodel/services/root-directory.service.spec.ts b/client/src/app/metamodel/services/root-directory.service.spec.ts deleted file mode 100644 index adf40e8..0000000 --- a/client/src/app/metamodel/services/root-directory.service.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { TestBed, inject } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; - -import { RootDirectoryService } from './root-directory.service'; -import { AppConfigService } from 'src/app/app-config.service'; -import { FileInfo } from '../models'; - -describe('[Instance][Metamodel][Services] RootDirectoryService', () => { - let service: RootDirectoryService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - { provide: AppConfigService, useValue: { apiUrl: 'http://testing.com' } }, - RootDirectoryService - ] - }); - service = TestBed.inject(RootDirectoryService); - }); - - it('#retrieveRootDirectory() should return an Observable', - inject([HttpTestingController, RootDirectoryService],(httpMock: HttpTestingController, service: RootDirectoryService) => { - const mockResponse = []; - - service.retrieveRootDirectory('/path').subscribe((event: FileInfo[]) => { - expect(event).toEqual(mockResponse); - }); - - const mockRequest = httpMock.expectOne('http://testing.com/file-explorer/path'); - - expect(mockRequest.cancelled).toBeFalsy(); - expect(mockRequest.request.method).toEqual('GET'); - expect(mockRequest.request.responseType).toEqual('json'); - mockRequest.flush(mockResponse); - - httpMock.verify(); - } - ) - ); -}); -- GitLab From 07d8e3826e9722bcb2adb7cae54964aee6761467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= Date: Tue, 29 Mar 2022 10:48:28 +0200 Subject: [PATCH 15/59] Css datatable + plot responsive --- .../result/datatable-tab.component.html | 58 ++++++++++--------- .../result/datatable-tab.component.scss | 8 +++ .../result/datatable-tab.component.ts | 1 + .../result/datatable.component.html | 2 +- 4 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 client/src/app/instance/search/components/result/datatable-tab.component.scss diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.html b/client/src/app/instance/search/components/result/datatable-tab.component.html index 1eaf244..9b4650c 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.html +++ b/client/src/app/instance/search/components/result/datatable-tab.component.html @@ -9,38 +9,42 @@
-
+
-
- - - - +
+
+
+ + + + +
+
diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.scss b/client/src/app/instance/search/components/result/datatable-tab.component.scss new file mode 100644 index 0000000..9628664 --- /dev/null +++ b/client/src/app/instance/search/components/result/datatable-tab.component.scss @@ -0,0 +1,8 @@ +.datatable-group > .row { + overflow-x: auto; + white-space: nowrap; +} +.datatable-group > .row > .col { + display: inline-block; + float: none; +} \ No newline at end of file diff --git a/client/src/app/instance/search/components/result/datatable-tab.component.ts b/client/src/app/instance/search/components/result/datatable-tab.component.ts index 8d3b912..a3e9844 100644 --- a/client/src/app/instance/search/components/result/datatable-tab.component.ts +++ b/client/src/app/instance/search/components/result/datatable-tab.component.ts @@ -19,6 +19,7 @@ import { Pagination, SearchQueryParams, Criterion, ConeSearch } from 'src/app/in @Component({ selector: 'app-datatable-tab', templateUrl: 'datatable-tab.component.html', + styleUrls: [ 'datatable-tab.component.scss' ], changeDetection: ChangeDetectionStrategy.OnPush }) export class DatatableTabComponent { diff --git a/client/src/app/instance/search/components/result/datatable.component.html b/client/src/app/instance/search/components/result/datatable.component.html index 6fac9c5..7533c54 100644 --- a/client/src/app/instance/search/components/result/datatable.component.html +++ b/client/src/app/instance/search/components/result/datatable.component.html @@ -1,6 +1,6 @@ -
+
-- GitLab From e6493865d0f4ffd95f36a99a9ac440b9435fcae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= Date: Tue, 29 Mar 2022 22:08:26 +0200 Subject: [PATCH 16/59] Add entity image (cone-search plot background) --- .../dataset/dataset-form.component.html | 53 ++++- .../dataset/dataset-form.component.ts | 30 ++- .../dataset/image-form.component.html | 49 ++++ .../dataset/image-form.component.ts | 54 +++++ .../dataset/components/dataset/index.ts | 4 +- .../containers/edit-dataset.component.html | 5 + .../containers/edit-dataset.component.ts | 19 +- .../app/metamodel/actions/image.actions.ts | 25 ++ .../app/metamodel/effects/image.effects.ts | 157 ++++++++++++ client/src/app/metamodel/effects/index.ts | 4 +- client/src/app/metamodel/metamodel.reducer.ts | 5 +- .../src/app/metamodel/models/image.model.ts | 26 ++ client/src/app/metamodel/models/index.ts | 3 +- .../app/metamodel/reducers/image.reducer.ts | 84 +++++++ .../app/metamodel/selectors/image.selector.ts | 48 ++++ .../app/metamodel/services/image.service.ts | 70 ++++++ client/src/app/metamodel/services/index.ts | 4 +- server/app/dependencies.php | 8 + server/app/routes.php | 2 + server/src/Action/ImageAction.php | 122 ++++++++++ server/src/Action/ImageListAction.php | 123 ++++++++++ server/src/Entity/Image.php | 223 ++++++++++++++++++ 22 files changed, 1098 insertions(+), 20 deletions(-) create mode 100644 client/src/app/admin/instance/dataset/components/dataset/image-form.component.html create mode 100644 client/src/app/admin/instance/dataset/components/dataset/image-form.component.ts create mode 100644 client/src/app/metamodel/actions/image.actions.ts create mode 100644 client/src/app/metamodel/effects/image.effects.ts create mode 100644 client/src/app/metamodel/models/image.model.ts create mode 100644 client/src/app/metamodel/reducers/image.reducer.ts create mode 100644 client/src/app/metamodel/selectors/image.selector.ts create mode 100644 client/src/app/metamodel/services/image.service.ts create mode 100644 server/src/Action/ImageAction.php create mode 100644 server/src/Action/ImageListAction.php create mode 100644 server/src/Entity/Image.php diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html index 5680185..788e929 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html @@ -96,9 +96,38 @@
- +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
File pathFile sizeRa [min,max]Dec [min,max]StretchPminPmax
{{ image.file_path }}{{ image.file_size }}[{{ image.ra_min }},{{ image.ra_max }}][{{ image.dec_min }},{{ image.dec_max }}]{{ image.stretch }}{{ image.pmin }}{{ image.pmax }}
+
@@ -169,3 +198,25 @@
+ + + + + \ No newline at end of file diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts index fa37612..11dbd54 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.ts @@ -7,10 +7,13 @@ * file that was distributed with this source code. */ -import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges, TemplateRef} from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms'; -import { Instance, Dataset, Survey, DatasetFamily, FileInfo, Attribute } from 'src/app/metamodel/models'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; + +import { Instance, Dataset, Survey, DatasetFamily, FileInfo, Attribute, Image } from 'src/app/metamodel/models'; @Component({ selector: 'app-dataset-form', @@ -29,11 +32,17 @@ export class DatasetFormComponent implements OnInit, OnChanges { @Input() rootDirectoryIsLoading: boolean; @Input() rootDirectoryIsLoaded: boolean; @Input() attributeList: Attribute[]; + @Input() imageList: Image[]; + @Input() imageListIsLoading: boolean; + @Input() imageListIsLoaded: boolean; @Output() changeSurvey: EventEmitter = new EventEmitter(); @Output() loadRootDirectory: EventEmitter = new EventEmitter(); + @Output() addNewImage: EventEmitter = new EventEmitter(); + @Output() editImage: EventEmitter = new EventEmitter(); @Output() onSubmit: EventEmitter = new EventEmitter(); public isNewDataset = true; + modalRef: BsModalRef; public form = new FormGroup({ name: new FormControl('', [Validators.required]), @@ -68,6 +77,8 @@ export class DatasetFormComponent implements OnInit, OnChanges { public: new FormControl('', [Validators.required]) }); + constructor(private modalService: BsModalService) { } + ngOnInit() { if (this.dataset) { this.isNewDataset = false; @@ -104,6 +115,10 @@ export class DatasetFormComponent implements OnInit, OnChanges { this.onSubmit.emit(this.form.getRawValue()); } } + + openModal(template: TemplateRef) { + this.modalRef = this.modalService.show(template); + } onChangeSurvey() { const surveyName = this.form.controls.survey_name.value; @@ -133,23 +148,12 @@ export class DatasetFormComponent implements OnInit, OnChanges { this.form.controls.cone_search_column_ra.enable(); this.form.controls.cone_search_column_dec.enable(); this.form.controls.cone_search_plot_enabled.enable(); - this.checkConeSearchPlotDisableOpened(); } else { this.form.controls.cone_search_opened.setValue(false); this.form.controls.cone_search_opened.disable(); this.form.controls.cone_search_column_ra.disable(); this.form.controls.cone_search_column_dec.disable(); this.form.controls.cone_search_plot_enabled.disable(); - this.form.controls.cone_search_plot_opened.disable(); - } - } - - checkConeSearchPlotDisableOpened() { - if (this.form.controls.cone_search_plot_enabled.value) { - this.form.controls.cone_search_plot_opened.enable(); - } else { - this.form.controls.cone_search_plot_enabled.setValue(false); - this.form.controls.cone_search_plot_opened.disable(); } } diff --git a/client/src/app/admin/instance/dataset/components/dataset/image-form.component.html b/client/src/app/admin/instance/dataset/components/dataset/image-form.component.html new file mode 100644 index 0000000..9498c43 --- /dev/null +++ b/client/src/app/admin/instance/dataset/components/dataset/image-form.component.html @@ -0,0 +1,49 @@ +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
\ No newline at end of file diff --git a/client/src/app/admin/instance/dataset/components/dataset/image-form.component.ts b/client/src/app/admin/instance/dataset/components/dataset/image-form.component.ts new file mode 100644 index 0000000..4b14b41 --- /dev/null +++ b/client/src/app/admin/instance/dataset/components/dataset/image-form.component.ts @@ -0,0 +1,54 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { FormGroup, FormControl, Validators } from '@angular/forms'; + +import { Image, FileInfo } from 'src/app/metamodel/models'; + +@Component({ + selector: 'app-image-form', + templateUrl: 'image-form.component.html' +}) +export class ImageFormComponent { + @Input() image: Image; + @Input() datasetDataPath: string; + @Input() files: FileInfo[]; + @Input() directoryIsLoading: boolean; + @Input() directoryIsLoaded: boolean; + @Output() loadRootDirectory: EventEmitter = new EventEmitter(); + @Output() onSubmit: EventEmitter = new EventEmitter(); + + public form = new FormGroup({ + file_path: new FormControl('', [Validators.required]), + file_size: new FormControl('', [Validators.required]), + ra_min: new FormControl('', [Validators.required]), + ra_max: new FormControl('', [Validators.required]), + dec_min: new FormControl('', [Validators.required]), + dec_max: new FormControl('', [Validators.required]), + stretch: new FormControl('', [Validators.required]), + pmin: new FormControl('', [Validators.required]), + pmax: new FormControl('', [Validators.required]) + }); + + onChangeFileSelect(path: string) { + this.loadRootDirectory.emit(`${this.datasetDataPath}${path}`); + } + + submit() { + if (this.image) { + this.onSubmit.emit({ + ...this.image, + ...this.form.getRawValue() + }); + } else { + this.onSubmit.emit(this.form.getRawValue()); + } + } +} diff --git a/client/src/app/admin/instance/dataset/components/dataset/index.ts b/client/src/app/admin/instance/dataset/components/dataset/index.ts index a877b8c..920f906 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/index.ts +++ b/client/src/app/admin/instance/dataset/components/dataset/index.ts @@ -9,8 +9,10 @@ import { DatasetCardComponent } from './dataset-card.component'; import { DatasetFormComponent } from './dataset-form.component'; +import { ImageFormComponent } from './image-form.component'; export const datasetComponents = [ DatasetCardComponent, - DatasetFormComponent + DatasetFormComponent, + ImageFormComponent ]; diff --git a/client/src/app/admin/instance/dataset/containers/edit-dataset.component.html b/client/src/app/admin/instance/dataset/containers/edit-dataset.component.html index ab9dd10..82b3218 100644 --- a/client/src/app/admin/instance/dataset/containers/edit-dataset.component.html +++ b/client/src/app/admin/instance/dataset/containers/edit-dataset.component.html @@ -37,8 +37,13 @@ [rootDirectoryIsLoading]="rootDirectoryIsLoading | async" [rootDirectoryIsLoaded]="rootDirectoryIsLoaded | async" [attributeList]="attributeList | async" + [imageList]="imageList | async" + [imageListIsLoading]="imageListIsLoading | async" + [imageListIsLoaded]="imageListIsLoaded | async" (changeSurvey)="loadTableList($event)" (loadRootDirectory)="loadRootDirectory($event)" + (addNewImage)="addNewImage($event)" + (editImage)="editImage($event)" (onSubmit)="editDataset($event)" #formDataset>
@@ -55,6 +55,9 @@ diff --git a/client/src/app/admin/admin-shared/components/file-select-form-control.component.ts b/client/src/app/admin/admin-shared/components/path-select-form-control.component.ts similarity index 74% rename from client/src/app/admin/admin-shared/components/file-select-form-control.component.ts rename to client/src/app/admin/admin-shared/components/path-select-form-control.component.ts index 5c4d0fc..4f7926e 100644 --- a/client/src/app/admin/admin-shared/components/file-select-form-control.component.ts +++ b/client/src/app/admin/admin-shared/components/path-select-form-control.component.ts @@ -16,10 +16,10 @@ import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; import { FileInfo } from 'src/app/admin/store/models'; @Component({ - selector: 'app-file-select-form-control', - templateUrl: 'file-select-form-control.component.html' + selector: 'app-path-select-form-control', + templateUrl: 'path-select-form-control.component.html' }) -export class FileSelectFormControlComponent implements OnChanges { +export class PathSelectFormControlComponent implements OnChanges { @Input() form: FormGroup; @Input() disabled: boolean = false; @Input() controlName: string; @@ -27,6 +27,7 @@ export class FileSelectFormControlComponent implements OnChanges { @Input() files: FileInfo[]; @Input() filesIsLoading: boolean; @Input() filesIsLoaded: boolean; + @Input() selectType: string; @Output() loadDirectory: EventEmitter = new EventEmitter(); @Output() select: EventEmitter = new EventEmitter(); @@ -77,19 +78,36 @@ export class FileSelectFormControlComponent implements OnChanges { } buildFilePath(fileInfo: FileInfo) { - let fileSelected = ''; + let fullPath = ''; if (this.fileExplorerPath !== '') { - fileSelected += `${this.fileExplorerPath}/`; + fullPath += `${this.fileExplorerPath}/`; + } else { + fullPath += '/'; } - return `${fileSelected}${fileInfo.name}`; + return `${fullPath}${fileInfo.name}`; } checkFileSelected(fileInfo: FileInfo) { - return this.buildFilePath(fileInfo) === this.fileSelected; + return this.buildFilePath(fileInfo) === this.fileSelected && this.selectType === 'file'; } - selectFile() { - this.form.controls[this.controlName].setValue(this.fileSelected); + checkPointer(fileInfo: FileInfo) { + if (this.selectType === 'file' && fileInfo.name !== '.') { + return true; + } else if (this.selectType === 'directory' && fileInfo.type === 'dir' && fileInfo.name !== '.') { + return true; + } else { + return false; + } + } + + onSubmit() { + if (this.selectType === 'file') { + this.form.controls[this.controlName].setValue(this.fileSelected); + } else { + this.form.controls[this.controlName].setValue(this.fileExplorerPath); + } + this.form.markAsDirty(); this.select.emit(this.fileInfoSelected); this.modalRef.hide(); diff --git a/client/src/app/admin/instance/components/instance-form.component.html b/client/src/app/admin/instance/components/instance-form.component.html index 8c9a484..4171276 100644 --- a/client/src/app/admin/instance/components/instance-form.component.html +++ b/client/src/app/admin/instance/components/instance-form.component.html @@ -17,13 +17,27 @@ - - + [selectType]="'directory'" + (loadDirectory)="loadRootDirectory.emit($event)"> + + +
@@ -34,16 +48,17 @@
- - +
@@ -64,26 +79,28 @@
- - - + - +
@@ -97,15 +114,17 @@
- - +
diff --git a/client/src/app/admin/instance/components/instance-form.component.ts b/client/src/app/admin/instance/components/instance-form.component.ts index a9d6620..cd351cb 100644 --- a/client/src/app/admin/instance/components/instance-form.component.ts +++ b/client/src/app/admin/instance/components/instance-form.component.ts @@ -31,6 +31,7 @@ export class InstanceFormComponent implements OnInit { description: new FormControl('', [Validators.required]), display: new FormControl('', [Validators.required]), data_path: new FormControl(''), + files_path: new FormControl(''), public: new FormControl(true, [Validators.required]), portal_logo: new FormControl(''), design_color: new FormControl('#7AC29A', [Validators.required]), @@ -75,8 +76,16 @@ the fast implementation of a project data exchange platform in a dedicated infor return this.form.controls.data_path.value == ''; } + isFilesPathEmpty() { + return this.form.controls.files_path.value == ''; + } + + onChangeFilesPath(path: string) { + this.loadRootDirectory.emit(`${this.form.controls.data_path.value}${path}`) + } + onChangeFileSelect(path: string) { - this.loadRootDirectory.emit(`${this.form.controls.data_path.value}${path}`); + this.loadRootDirectory.emit(`${this.form.controls.data_path.value}${this.form.controls.files_path.value}${path}`); } getHomeConfigFormGroup() { diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html index 425366e..0200ca1 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-form.component.html @@ -40,13 +40,16 @@ - - + [selectType]="'directory'" + (loadDirectory)="onChangeDataPath($event)"> +
diff --git a/client/src/app/admin/instance/dataset/components/image/image-form.component.html b/client/src/app/admin/instance/dataset/components/image/image-form.component.html index da9163c..1c8392e 100644 --- a/client/src/app/admin/instance/dataset/components/image/image-form.component.html +++ b/client/src/app/admin/instance/dataset/components/image/image-form.component.html @@ -1,16 +1,17 @@
- - +
diff --git a/client/src/app/admin/store/actions/admin-file-explorer.actions.ts b/client/src/app/admin/store/actions/admin-file-explorer.actions.ts index d73c963..e54fda8 100644 --- a/client/src/app/admin/store/actions/admin-file-explorer.actions.ts +++ b/client/src/app/admin/store/actions/admin-file-explorer.actions.ts @@ -11,6 +11,6 @@ import { createAction, props } from '@ngrx/store'; import { FileInfo } from '../models'; -export const loadFiles = createAction('[Metamodel] Load Files', props<{ path: string }>()); -export const loadFilesSuccess = createAction('[Metamodel] Load Files Success', props<{ files: FileInfo[] }>()); -export const loadFilesFail = createAction('[Metamodel] Load Files Fail'); \ No newline at end of file +export const loadFiles = createAction('[Admin] Load Files', props<{ path: string }>()); +export const loadFilesSuccess = createAction('[Admin] Load Files Success', props<{ files: FileInfo[] }>()); +export const loadFilesFail = createAction('[Admin] Load Files Fail'); \ No newline at end of file diff --git a/client/src/app/admin/store/actions/attribute-distinct.actions.ts b/client/src/app/admin/store/actions/attribute-distinct.actions.ts index 59f6d37..0ca8bb7 100644 --- a/client/src/app/admin/store/actions/attribute-distinct.actions.ts +++ b/client/src/app/admin/store/actions/attribute-distinct.actions.ts @@ -11,6 +11,6 @@ import { createAction, props } from '@ngrx/store'; import { Attribute } from 'src/app/metamodel/models'; -export const loadAttributeDistinctList = createAction('[Metamodel] Load Attribute Distinct List', props<{ attribute: Attribute }>()); -export const loadAttributeDistinctListSuccess = createAction('[Metamodel] Load Attribute List Distinct Success', props<{ values: string[] }>()); -export const loadAttributeDistinctListFail = createAction('[Metamodel] Load Attribute List Distinct Fail'); +export const loadAttributeDistinctList = createAction('[Admin] Load Attribute Distinct List', props<{ attribute: Attribute }>()); +export const loadAttributeDistinctListSuccess = createAction('[Admin] Load Attribute List Distinct Success', props<{ values: string[] }>()); +export const loadAttributeDistinctListFail = createAction('[Admin] Load Attribute List Distinct Fail'); diff --git a/client/src/app/admin/store/actions/column.actions.ts b/client/src/app/admin/store/actions/column.actions.ts index 77ccf23..7cfb955 100644 --- a/client/src/app/admin/store/actions/column.actions.ts +++ b/client/src/app/admin/store/actions/column.actions.ts @@ -11,6 +11,6 @@ import { createAction, props } from '@ngrx/store'; import { Column } from '../models'; -export const loadColumnList = createAction('[Metamodel] Load Column List'); -export const loadColumnListSuccess = createAction('[Metamodel] Load Column List Success', props<{ columns: Column[] }>()); -export const loadColumnListFail = createAction('[Metamodel] Load Column List Fail'); +export const loadColumnList = createAction('[Admin] Load Column List'); +export const loadColumnListSuccess = createAction('[Admin] Load Column List Success', props<{ columns: Column[] }>()); +export const loadColumnListFail = createAction('[Admin] Load Column List Fail'); diff --git a/client/src/app/admin/store/actions/table.actions.ts b/client/src/app/admin/store/actions/table.actions.ts index 2e10cc5..6538d57 100644 --- a/client/src/app/admin/store/actions/table.actions.ts +++ b/client/src/app/admin/store/actions/table.actions.ts @@ -9,6 +9,6 @@ import { createAction, props } from '@ngrx/store'; -export const loadTableList = createAction('[Metamodel] Load Table List', props<{ idDatabase: number }>()); -export const loadTableListSuccess = createAction('[Metamodel] Load Table List Success', props<{ tables: string[] }>()); -export const loadTableListFail = createAction('[Metamodel] Load Table List Fail'); +export const loadTableList = createAction('[Admin] Load Table List', props<{ idDatabase: number }>()); +export const loadTableListSuccess = createAction('[Admin] Load Table List Success', props<{ tables: string[] }>()); +export const loadTableListFail = createAction('[Admin] Load Table List Fail'); diff --git a/client/src/app/instance/home/components/welcome.component.ts b/client/src/app/instance/home/components/welcome.component.ts index 6eaf4c5..316ec2b 100644 --- a/client/src/app/instance/home/components/welcome.component.ts +++ b/client/src/app/instance/home/components/welcome.component.ts @@ -30,6 +30,6 @@ export class WelcomeComponent { * @return string */ getLogoSrc(): string { - return `${this.apiUrl}/download-instance-file/${this.instance.name}/${this.instance.home_component_config.home_component_logo}`; + return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${this.instance.home_component_config.home_component_logo}`; } } diff --git a/client/src/app/instance/instance.component.ts b/client/src/app/instance/instance.component.ts index ed46474..fe75d5f 100644 --- a/client/src/app/instance/instance.component.ts +++ b/client/src/app/instance/instance.component.ts @@ -73,7 +73,7 @@ export class InstanceComponent implements OnInit, OnDestroy { this.links.push({ label: instance.documentation_label, icon: 'fas fa-question', routerLink: 'documentation' }); } if (instance.design_favicon !== '') { - this.favIcon.href = `${this.config.apiUrl}/download-instance-file/${instance.name}/${instance.design_favicon}`; + this.favIcon.href = `${this.config.apiUrl}/instance/${instance.name}/file-explorer${instance.design_favicon}`; } this.title.innerHTML = instance.label; } diff --git a/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts b/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts index 9cdb554..e2cfc9a 100644 --- a/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts +++ b/client/src/app/instance/search/components/result/renderer/download-renderer.component.ts @@ -37,7 +37,11 @@ export class DownloadRendererComponent { * @return string */ getHref(): string { - return `${getHost(this.appConfig.apiUrl)}/download-file/${this.datasetName}/${this.value}`; + let path = this.value; + if (path[0] !== '/') { + path = '/' + path; + } + return `${getHost(this.appConfig.apiUrl)}/dataset/${this.datasetName}/file-explorer${path}`; } /** diff --git a/client/src/app/portal/components/instance-card.component.ts b/client/src/app/portal/components/instance-card.component.ts index 28263db..7269b4e 100644 --- a/client/src/app/portal/components/instance-card.component.ts +++ b/client/src/app/portal/components/instance-card.component.ts @@ -32,6 +32,6 @@ export class InstanceCardComponent { * @return string */ getLogoSrc(): string { - return `${this.apiUrl}/download-instance-file/${this.instance.name}/${this.instance.portal_logo}`; + return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${this.instance.portal_logo}`; } } diff --git a/client/src/app/shared/components/navbar.component.ts b/client/src/app/shared/components/navbar.component.ts index 24d37cb..5af1e0e 100644 --- a/client/src/app/shared/components/navbar.component.ts +++ b/client/src/app/shared/components/navbar.component.ts @@ -67,7 +67,7 @@ export class NavbarComponent { */ getLogoHref(): string { if (this.instance.design_logo) { - return `${this.apiUrl}/download-instance-file/${this.instance.name}/${this.instance.design_logo}`; + return `${this.apiUrl}/instance/${this.instance.name}/file-explorer${this.instance.design_logo}`; } return 'assets/cesam_anis40.png'; } diff --git a/conf-dev/create-db.sh b/conf-dev/create-db.sh index fed4add..28a54e9 100644 --- a/conf-dev/create-db.sh +++ b/conf-dev/create-db.sh @@ -60,7 +60,7 @@ curl -d '{"label":"Spectra graph","value":"spectra_graph","display":20,"select_n curl -d '{"label":"Test","dbname":"anis_test","dbtype":"pdo_pgsql","dbhost":"db","dbport":5432,"dblogin":"anis","dbpassword":"anis"}' --header 'Content-Type: application/json' -X POST http://localhost/database # Add default instance -curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","display":10,"data_path":"\/DEFAULT","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"logo.png","design_favicon":"favicon.ico","home_component":"WelcomeComponent","home_component_config":{"home_component_text":"AstroNomical Information System","home_component_logo":"home_component_logo.png"},"samp_enabled":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance +curl -d '{"name":"default","label":"Default instance","description":"Instance for the test","display":10,"data_path":"\/DEFAULT","files_path":"\/INSTANCE_FILES","public":true,"portal_logo":"","design_color":"#7AC29A","design_background_color":"#ffffff","design_logo":"logo.png","design_favicon":"favicon.ico","home_component":"WelcomeComponent","home_component_config":{"home_component_text":"AstroNomical Information System","home_component_logo":"home_component_logo.png"},"samp_enabled":true,"search_by_criteria_allowed":true,"search_by_criteria_label":"Search","search_multiple_allowed":false,"search_multiple_label":"Search multiple","search_multiple_all_datasets_selected":false,"documentation_allowed":false,"documentation_label":"Documentation"}' --header 'Content-Type: application/json' -X POST http://localhost/instance # Add ANIS, SVOM and IRIS surveys curl -d '{"name":"anis_survey","label":"ANIS survey","description":"Survey used for testing","link":"https://anis.lam.fr","manager":"F. Agneray","id_database":1}' --header 'Content-Type: application/json' -X POST http://localhost/survey diff --git a/server/app/dependencies.php b/server/app/dependencies.php index fdd1594..366ed50 100644 --- a/server/app/dependencies.php +++ b/server/app/dependencies.php @@ -145,6 +145,10 @@ $container->set('App\Action\InstanceAction', function (ContainerInterface $c) { return new App\Action\InstanceAction($c->get('em')); }); +$container->set('App\Action\InstanceFileExplorerAction', function (ContainerInterface $c) { + return new App\Action\InstanceFileExplorerAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']); +}); + $container->set('App\Action\DatasetFamilyListAction', function (ContainerInterface $c) { return new App\Action\DatasetFamilyListAction($c->get('em')); }); @@ -260,11 +264,3 @@ $container->set('App\Action\DownloadArchiveAction', function (ContainerInterface $container->set('App\Action\DatasetFileExplorerAction', function (ContainerInterface $c) { return new App\Action\DatasetFileExplorerAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']); }); - -$container->set('App\Action\DownloadInstanceFileAction', function (ContainerInterface $c) { - return new App\Action\DownloadInstanceFileAction($c->get('em'), $c->get('settings')['data_path']); -}); - -$container->set('App\Action\DownloadFileAction', function (ContainerInterface $c) { - return new App\Action\DownloadFileAction($c->get('em'), $c->get('settings')['data_path'], $c->get(SETTINGS)['token']); -}); diff --git a/server/app/routes.php b/server/app/routes.php index 6247ccc..b4e79e3 100644 --- a/server/app/routes.php +++ b/server/app/routes.php @@ -39,6 +39,7 @@ $app->group('', function (RouteCollectorProxy $group) { $group->map([OPTIONS, GET, PUT, DELETE], '/survey/{name}', App\Action\SurveyAction::class); $group->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class); $group->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class); + $group->map([OPTIONS, GET], '/instance/{name}/file-explorer[{fpath:.*}]', App\Action\InstanceFileExplorerAction::class); $group->map([OPTIONS, GET, POST], '/instance/{name}/group', App\Action\GroupListAction::class); $group->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class); $group->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\DatasetListByInstanceAction::class); @@ -46,6 +47,7 @@ $app->group('', function (RouteCollectorProxy $group) { $group->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class); $group->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class); $group->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class); + $group->map([OPTIONS, GET], '/dataset/{name}/file-explorer[{fpath:.*}]', App\Action\DatasetFileExplorerAction::class); $group->map([OPTIONS, GET, POST], '/dataset/{name}/criteria-family', App\Action\CriteriaFamilyListAction::class); $group->map([OPTIONS, GET, PUT, DELETE], '/criteria-family/{id}', App\Action\CriteriaFamilyAction::class); $group->map([OPTIONS, GET, POST], '/dataset/{name}/output-family', App\Action\OutputFamilyListAction::class); @@ -76,6 +78,7 @@ $app->group('', function (RouteCollectorProxy $group) { // Search actions $app->get('/search/{dname}', App\Action\SearchAction::class); +// Result actions $app->get('/start-task-create-result/{dname}', App\Action\StartTaskCreateResultAction::class); $app->get('/is-result-available/{id}', App\Action\IsResultAvailableAction::class); $app->get('/download-result/{dname}/{id}', App\Action\DownloadResultAction::class); @@ -84,8 +87,3 @@ $app->get('/download-result/{dname}/{id}', App\Action\DownloadResultAction::clas $app->get('/start-task-create-archive/{dname}', App\Action\StartTaskCreateArchiveAction::class); $app->get('/is-archive-available/{id}', App\Action\IsArchiveAvailableAction::class); $app->get('/download-archive/{dname}/{id}', App\Action\DownloadArchiveAction::class); - -// Explore and download individual files -$app->get('/dataset-file-explorer/{dname}[{fpath:.*}]', App\Action\DatasetFileExplorerAction::class); -$app->get('/download-instance-file/{iname}/[{fpath:.*}]', App\Action\DownloadInstanceFileAction::class); -$app->get('/download-file/{dname}/[{fpath:.*}]', App\Action\DownloadFileAction::class); diff --git a/server/doctrine-proxy/__CG__AppEntityDataset.php b/server/doctrine-proxy/__CG__AppEntityDataset.php index eb60eb6..8b3dd79 100644 --- a/server/doctrine-proxy/__CG__AppEntityDataset.php +++ b/server/doctrine-proxy/__CG__AppEntityDataset.php @@ -67,10 +67,10 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy public function __sleep() { if ($this->__isInitialized__) { - return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'coneSearchPlotOpened', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; + return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'downloadEnabled', 'downloadOpened', 'downloadJson', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; } - return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'coneSearchPlotOpened', 'downloadEnabled', 'downloadOpened', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; + return ['__isInitialized__', 'name', 'tableRef', 'label', 'description', 'display', 'dataPath', 'public', 'infoSurveyEnabled', 'infoSurveyLabel', 'coneSearchEnabled', 'coneSearchOpened', 'coneSearchColumnRa', 'coneSearchColumnDec', 'coneSearchPlotEnabled', 'downloadEnabled', 'downloadOpened', 'downloadJson', 'downloadCsv', 'downloadAscii', 'downloadVo', 'downloadArchive', 'summaryEnabled', 'summaryOpened', 'serverLinkEnabled', 'serverLinkOpened', 'datatableEnabled', 'datatableOpened', 'datatableSelectableRows', 'survey', 'datasetFamily', 'attributes']; } /** @@ -492,67 +492,67 @@ class Dataset extends \App\Entity\Dataset implements \Doctrine\ORM\Proxy\Proxy /** * {@inheritDoc} */ - public function getConeSearchPlotOpened() + public function getDownloadEnabled() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getConeSearchPlotOpened', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadEnabled', []); - return parent::getConeSearchPlotOpened(); + return parent::getDownloadEnabled(); } /** * {@inheritDoc} */ - public function setConeSearchPlotOpened($coneSearchPlotOpened) + public function setDownloadEnabled($downloadEnabled) { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'setConeSearchPlotOpened', [$coneSearchPlotOpened]); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadEnabled', [$downloadEnabled]); - return parent::setConeSearchPlotOpened($coneSearchPlotOpened); + return parent::setDownloadEnabled($downloadEnabled); } /** * {@inheritDoc} */ - public function getDownloadEnabled() + public function getDownloadOpened() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadEnabled', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadOpened', []); - return parent::getDownloadEnabled(); + return parent::getDownloadOpened(); } /** * {@inheritDoc} */ - public function setDownloadEnabled($downloadEnabled) + public function setDownloadOpened($downloadOpened) { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadEnabled', [$downloadEnabled]); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadOpened', [$downloadOpened]); - return parent::setDownloadEnabled($downloadEnabled); + return parent::setDownloadOpened($downloadOpened); } /** * {@inheritDoc} */ - public function getDownloadOpened() + public function getDownloadJson() { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadOpened', []); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDownloadJson', []); - return parent::getDownloadOpened(); + return parent::getDownloadJson(); } /** * {@inheritDoc} */ - public function setDownloadOpened($downloadOpened) + public function setDownloadJson($downloadJson) { - $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadOpened', [$downloadOpened]); + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDownloadJson', [$downloadJson]); - return parent::setDownloadOpened($downloadOpened); + return parent::setDownloadJson($downloadJson); } /** diff --git a/server/doctrine-proxy/__CG__AppEntityImage.php b/server/doctrine-proxy/__CG__AppEntityImage.php new file mode 100644 index 0000000..31bc931 --- /dev/null +++ b/server/doctrine-proxy/__CG__AppEntityImage.php @@ -0,0 +1,415 @@ + properties to be lazy loaded, indexed by property name + */ + public static $lazyPropertiesNames = array ( +); + + /** + * @var array default values of properties to be lazy loaded, with keys being the property names + * + * @see \Doctrine\Common\Proxy\Proxy::__getLazyProperties + */ + public static $lazyPropertiesDefaults = array ( +); + + + + public function __construct(?\Closure $initializer = null, ?\Closure $cloner = null) + { + + $this->__initializer__ = $initializer; + $this->__cloner__ = $cloner; + } + + + + + + + + /** + * + * @return array + */ + public function __sleep() + { + if ($this->__isInitialized__) { + return ['__isInitialized__', 'id', 'filePath', 'fileSize', 'raMin', 'raMax', 'decMin', 'decMax', 'stretch', 'pmin', 'pmax', 'dataset']; + } + + return ['__isInitialized__', 'id', 'filePath', 'fileSize', 'raMin', 'raMax', 'decMin', 'decMax', 'stretch', 'pmin', 'pmax', 'dataset']; + } + + /** + * + */ + public function __wakeup() + { + if ( ! $this->__isInitialized__) { + $this->__initializer__ = function (Image $proxy) { + $proxy->__setInitializer(null); + $proxy->__setCloner(null); + + $existingProperties = get_object_vars($proxy); + + foreach ($proxy::$lazyPropertiesDefaults as $property => $defaultValue) { + if ( ! array_key_exists($property, $existingProperties)) { + $proxy->$property = $defaultValue; + } + } + }; + + } + } + + /** + * + */ + public function __clone() + { + $this->__cloner__ && $this->__cloner__->__invoke($this, '__clone', []); + } + + /** + * Forces initialization of the proxy + */ + public function __load() + { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__load', []); + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __isInitialized() + { + return $this->__isInitialized__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitialized($initialized) + { + $this->__isInitialized__ = $initialized; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitializer(\Closure $initializer = null) + { + $this->__initializer__ = $initializer; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __getInitializer() + { + return $this->__initializer__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setCloner(\Closure $cloner = null) + { + $this->__cloner__ = $cloner; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific cloning logic + */ + public function __getCloner() + { + return $this->__cloner__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + * @deprecated no longer in use - generated code now relies on internal components rather than generated public API + * @static + */ + public function __getLazyProperties() + { + return self::$lazyPropertiesDefaults; + } + + + /** + * {@inheritDoc} + */ + public function getId() + { + if ($this->__isInitialized__ === false) { + return (int) parent::getId(); + } + + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []); + + return parent::getId(); + } + + /** + * {@inheritDoc} + */ + public function getFilePath() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getFilePath', []); + + return parent::getFilePath(); + } + + /** + * {@inheritDoc} + */ + public function setFilePath($filePath) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setFilePath', [$filePath]); + + return parent::setFilePath($filePath); + } + + /** + * {@inheritDoc} + */ + public function getFileSize() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getFileSize', []); + + return parent::getFileSize(); + } + + /** + * {@inheritDoc} + */ + public function setFileSize($fileSize) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setFileSize', [$fileSize]); + + return parent::setFileSize($fileSize); + } + + /** + * {@inheritDoc} + */ + public function getRaMin() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getRaMin', []); + + return parent::getRaMin(); + } + + /** + * {@inheritDoc} + */ + public function setRaMin($raMin) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setRaMin', [$raMin]); + + return parent::setRaMin($raMin); + } + + /** + * {@inheritDoc} + */ + public function getRaMax() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getRaMax', []); + + return parent::getRaMax(); + } + + /** + * {@inheritDoc} + */ + public function setRaMax($raMax) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setRaMax', [$raMax]); + + return parent::setRaMax($raMax); + } + + /** + * {@inheritDoc} + */ + public function getDecMin() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDecMin', []); + + return parent::getDecMin(); + } + + /** + * {@inheritDoc} + */ + public function setDecMin($decMin) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDecMin', [$decMin]); + + return parent::setDecMin($decMin); + } + + /** + * {@inheritDoc} + */ + public function getDecMax() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDecMax', []); + + return parent::getDecMax(); + } + + /** + * {@inheritDoc} + */ + public function setDecMax($decMax) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setDecMax', [$decMax]); + + return parent::setDecMax($decMax); + } + + /** + * {@inheritDoc} + */ + public function getStretch() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getStretch', []); + + return parent::getStretch(); + } + + /** + * {@inheritDoc} + */ + public function setStretch($stretch) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setStretch', [$stretch]); + + return parent::setStretch($stretch); + } + + /** + * {@inheritDoc} + */ + public function getPmin() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getPmin', []); + + return parent::getPmin(); + } + + /** + * {@inheritDoc} + */ + public function setPmin($pmin) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setPmin', [$pmin]); + + return parent::setPmin($pmin); + } + + /** + * {@inheritDoc} + */ + public function getPmax() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getPmax', []); + + return parent::getPmax(); + } + + /** + * {@inheritDoc} + */ + public function setPmax($pmax) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setPmax', [$pmax]); + + return parent::setPmax($pmax); + } + + /** + * {@inheritDoc} + */ + public function getDataset() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getDataset', []); + + return parent::getDataset(); + } + + /** + * {@inheritDoc} + */ + public function jsonSerialize(): array + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'jsonSerialize', []); + + return parent::jsonSerialize(); + } + +} diff --git a/server/doctrine-proxy/__CG__AppEntityInstance.php b/server/doctrine-proxy/__CG__AppEntityInstance.php index d5f1d1d..7b3ee86 100644 --- a/server/doctrine-proxy/__CG__AppEntityInstance.php +++ b/server/doctrine-proxy/__CG__AppEntityInstance.php @@ -67,10 +67,10 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy public function __sleep() { if ($this->__isInitialized__) { - return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; + return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'filesPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; } - return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; + return ['__isInitialized__', 'name', 'label', 'description', 'display', 'dataPath', 'filesPath', 'public', 'portalLogo', 'designColor', 'designBackgroundColor', 'designLogo', 'designFavicon', 'homeComponent', 'homeComponentConfig', 'sampEnabled', 'searchByCriteriaAllowed', 'searchByCriteriaLabel', 'searchMultipleAllowed', 'searchMultipleLabel', 'searchMultipleAllDatasetsSelected', 'documentationAllowed', 'documentationLabel', 'datasetFamilies']; } /** @@ -280,6 +280,28 @@ class Instance extends \App\Entity\Instance implements \Doctrine\ORM\Proxy\Proxy return parent::setDataPath($dataPath); } + /** + * {@inheritDoc} + */ + public function getFilesPath() + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'getFilesPath', []); + + return parent::getFilesPath(); + } + + /** + * {@inheritDoc} + */ + public function setFilesPath($filesPath) + { + + $this->__initializer__ && $this->__initializer__->__invoke($this, 'setFilesPath', [$filesPath]); + + return parent::setFilesPath($filesPath); + } + /** * {@inheritDoc} */ diff --git a/server/src/Action/AdminFileExplorerAction.php b/server/src/Action/AdminFileExplorerAction.php index 8f86b80..d3100f8 100644 --- a/server/src/Action/AdminFileExplorerAction.php +++ b/server/src/Action/AdminFileExplorerAction.php @@ -61,7 +61,7 @@ final class AdminFileExplorerAction $path = $this->dataPath; if (array_key_exists('fpath', $args)) { - $path .= DIRECTORY_SEPARATOR . $args['fpath']; + $path .= $args['fpath']; } if (!file_exists($path)) { diff --git a/server/src/Action/DatasetFileExplorerAction.php b/server/src/Action/DatasetFileExplorerAction.php index 077b0be..84dabe4 100644 --- a/server/src/Action/DatasetFileExplorerAction.php +++ b/server/src/Action/DatasetFileExplorerAction.php @@ -16,7 +16,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Doctrine\ORM\EntityManagerInterface; use Slim\Exception\HttpNotFoundException; -use Slim\Exception\HttpBadRequestException; +use Nyholm\Psr7\Factory\Psr17Factory; /** * @author François Agneray @@ -71,7 +71,7 @@ final class DatasetFileExplorerAction extends AbstractAction } // Search the correct dataset with primary key - $datasetName = $args['dname']; + $datasetName = $args['name']; $dataset = $this->em->find('App\Entity\Dataset', $datasetName); // If dataset is not found 404 @@ -102,17 +102,11 @@ final class DatasetFileExplorerAction extends AbstractAction } // Dataset data_path - $path = $this->dataPath . $dataset->getFullDataPath(); + $instancePath = $dataset->getDatasetFamily()->getInstance()->getDataPath(); + $path = $this->dataPath . $instancePath . $dataset->getDataPath(); if (array_key_exists('fpath', $args)) { - $path .= DIRECTORY_SEPARATOR . $args['fpath']; - } - - if (is_file($path)) { - throw new HttpBadRequestException( - $request, - 'The requested path is a file' - ); + $path .= $args['fpath']; } if (!file_exists($path)) { @@ -122,18 +116,28 @@ final class DatasetFileExplorerAction extends AbstractAction ); } - $files = array(); - - foreach (scandir($path) as $file) { - $files[] = array( - 'name' => $file, - 'size' => filesize($path . DIRECTORY_SEPARATOR . $file), - 'type' => filetype($path . DIRECTORY_SEPARATOR . $file), - 'mimetype' => mime_content_type($path . DIRECTORY_SEPARATOR . $file) - ); + if (is_file($path)) { + // If the file found so stream it + $psr17Factory = new Psr17Factory(); + $stream = $psr17Factory->createStreamFromFile($path, 'r'); + + return $response->withBody($stream) + ->withHeader('Content-Type', mime_content_type($path)) + ->withHeader('Content-Length', filesize($path)); + } else { + $files = array(); + + foreach (scandir($path) as $file) { + $files[] = array( + 'name' => $file, + 'size' => filesize($path . DIRECTORY_SEPARATOR . $file), + 'type' => filetype($path . DIRECTORY_SEPARATOR . $file), + 'mimetype' => mime_content_type($path . DIRECTORY_SEPARATOR . $file) + ); + } + + $response->getBody()->write(json_encode($files)); + return $response; } - - $response->getBody()->write(json_encode($files)); - return $response; } } diff --git a/server/src/Action/DownloadInstanceFileAction.php b/server/src/Action/DownloadInstanceFileAction.php deleted file mode 100644 index c450dea..0000000 --- a/server/src/Action/DownloadInstanceFileAction.php +++ /dev/null @@ -1,96 +0,0 @@ - - * @package App\Action - */ -final class DownloadInstanceFileAction extends AbstractAction -{ - /** - * Contains anis-server data path - * - * @var string - */ - private $dataPath; - - /** - * Create the classe before call __invoke to execute the action - * - * @param EntityManagerInterface $em Doctrine Entity Manager Interface - * @param string $dataPath Contains anis-server data path - */ - public function __construct(EntityManagerInterface $em, string $dataPath) - { - parent::__construct($em); - $this->dataPath = $dataPath; - } - - /** - * `GET` Returns the file found - * - * @param ServerRequestInterface $request PSR-7 This object represents the HTTP request - * @param ResponseInterface $response PSR-7 This object represents the HTTP response - * @param string[] $args This table contains information transmitted in the URL (see routes.php) - * - * @return ResponseInterface - */ - public function __invoke( - ServerRequestInterface $request, - ResponseInterface $response, - array $args - ): ResponseInterface { - if ($request->getMethod() === OPTIONS) { - return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); - } - - // Search the correct instance with primary key - $instanceName = $args['iname']; - $instance = $this->em->find('App\Entity\Instance', $instanceName); - - // If dataset is not found 404 - if (is_null($instance)) { - throw new HttpNotFoundException( - $request, - 'Instance with name ' . $instanceName . ' is not found' - ); - } - - // Search the file - $filePath = $this->dataPath . $instance->getDataPath() . DIRECTORY_SEPARATOR . $args['fpath']; - - // If the file not found 404 - if (!file_exists($filePath)) { - throw new HttpNotFoundException( - $request, - 'File with path ' . $args['fpath'] . ' is not found for the instance ' . $instanceName - ); - } - - // If the file found so stream it - $psr17Factory = new Psr17Factory(); - $stream = $psr17Factory->createStreamFromFile($filePath, 'r'); - - return $response->withBody($stream) - ->withHeader('Content-Disposition', 'attachment; filename=' . basename($filePath) . ';') - ->withHeader('Content-Type', mime_content_type($filePath)) - ->withHeader('Content-Length', filesize($filePath)); - } -} diff --git a/server/src/Action/InstanceAction.php b/server/src/Action/InstanceAction.php index 7f2b578..a9044e5 100644 --- a/server/src/Action/InstanceAction.php +++ b/server/src/Action/InstanceAction.php @@ -99,6 +99,7 @@ final class InstanceAction extends AbstractAction $instance->setDescription($parsedBody['description']); $instance->setDisplay($parsedBody['display']); $instance->setDataPath($parsedBody['data_path']); + $instance->setFilesPath($parsedBody['files_path']); $instance->setPublic($parsedBody['public']); $instance->setPortalLogo($parsedBody['portal_logo']); $instance->setDesignColor($parsedBody['design_color']); diff --git a/server/src/Action/DownloadFileAction.php b/server/src/Action/InstanceFileExplorerAction.php similarity index 54% rename from server/src/Action/DownloadFileAction.php rename to server/src/Action/InstanceFileExplorerAction.php index 196a0ad..f613a42 100644 --- a/server/src/Action/DownloadFileAction.php +++ b/server/src/Action/InstanceFileExplorerAction.php @@ -22,7 +22,7 @@ use Nyholm\Psr7\Factory\Psr17Factory; * @author François Agneray * @package App\Action */ -final class DownloadFileAction extends AbstractAction +final class InstanceFileExplorerAction extends AbstractAction { /** * Contains anis-server data path @@ -32,7 +32,7 @@ final class DownloadFileAction extends AbstractAction private $dataPath; /** - * Contains settings to handle Json Web Token + * Contains settings to handle Json Web Token (app/settings.php) * * @var array */ @@ -41,9 +41,9 @@ final class DownloadFileAction extends AbstractAction /** * Create the classe before call __invoke to execute the action * - * @param EntityManagerInterface $em Doctrine Entity Manager Interface - * @param string $dataPath Contains anis-server data path - * @param array $settings Settings about token + * @param EntityManagerInterface $em Doctrine Entity Manager Interface + * @param string $dataPath Contains anis-server data path + * @param array $settings Settings about token */ public function __construct(EntityManagerInterface $em, string $dataPath, array $settings) { @@ -53,7 +53,7 @@ final class DownloadFileAction extends AbstractAction } /** - * `GET` Returns the file found + * `GET` Returns the list of files if path is a directory or stream file * * @param ServerRequestInterface $request PSR-7 This object represents the HTTP request * @param ResponseInterface $response PSR-7 This object represents the HTTP response @@ -70,20 +70,18 @@ final class DownloadFileAction extends AbstractAction return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); } - // Search the correct dataset with primary key - $datasetName = $args['dname']; - $dataset = $this->em->find('App\Entity\Dataset', $datasetName); + // Search the correct instance with primary key + $instance = $this->em->find('App\Entity\Instance', $args['name']); - // If dataset is not found 404 - if (is_null($dataset)) { + // If instance is not found 404 + if (is_null($instance)) { throw new HttpNotFoundException( $request, - 'Dataset with name ' . $datasetName . ' is not found' + 'Instance with name ' . $args['name'] . ' is not found' ); } // If instance is private and authorization enabled - $instance = $dataset->getDatasetFamily()->getInstance(); if (!$instance->getPublic() && boolval($this->settings['enabled'])) { $this->verifyInstanceAuthorization( $request, @@ -92,33 +90,41 @@ final class DownloadFileAction extends AbstractAction ); } - // If dataset is private and authorization enabled - if (!$dataset->getPublic() && boolval($this->settings['enabled'])) { - $this->verifyDatasetAuthorization( - $request, - $dataset->getName(), - explode(',', $this->settings['admin_roles']) - ); - } + $path = $this->dataPath . $instance->getDataPath() . $instance->getFilesPath(); - // Search the file - $filePath = $this->dataPath . $dataset->getFullDataPath() . DIRECTORY_SEPARATOR . $args['fpath']; + if (array_key_exists('fpath', $args)) { + $path .= $args['fpath']; + } - // If the file not found 404 - if (!file_exists($filePath)) { + if (!file_exists($path)) { throw new HttpNotFoundException( $request, - 'File with path ' . $args['fpath'] . ' is not found for the dataset ' . $datasetName + 'The requested path is not found' ); } - // If the file found so stream it - $psr17Factory = new Psr17Factory(); - $stream = $psr17Factory->createStreamFromFile($filePath, 'r'); + if (is_file($path)) { + // If the file found so stream it + $psr17Factory = new Psr17Factory(); + $stream = $psr17Factory->createStreamFromFile($path, 'r'); + + return $response->withBody($stream) + ->withHeader('Content-Type', mime_content_type($path)) + ->withHeader('Content-Length', filesize($path)); + } else { + $files = array(); - return $response->withBody($stream) - ->withHeader('Content-Disposition', 'attachment; filename=' . basename($filePath) . ';') - ->withHeader('Content-Type', mime_content_type($filePath)) - ->withHeader('Content-Length', filesize($filePath)); + foreach (scandir($path) as $file) { + $files[] = array( + 'name' => $file, + 'size' => filesize($path . DIRECTORY_SEPARATOR . $file), + 'type' => filetype($path . DIRECTORY_SEPARATOR . $file), + 'mimetype' => mime_content_type($path . DIRECTORY_SEPARATOR . $file) + ); + } + + $response->getBody()->write(json_encode($files)); + return $response; + } } } diff --git a/server/src/Action/InstanceListAction.php b/server/src/Action/InstanceListAction.php index 1dc9f4c..4082537 100644 --- a/server/src/Action/InstanceListAction.php +++ b/server/src/Action/InstanceListAction.php @@ -132,6 +132,7 @@ final class InstanceListAction extends AbstractAction $instance->setDescription($parsedBody['description']); $instance->setDisplay($parsedBody['display']); $instance->setDataPath($parsedBody['data_path']); + $instance->setFilesPath($parsedBody['files_path']); $instance->setPublic($parsedBody['public']); $instance->setPortalLogo($parsedBody['portal_logo']); $instance->setDesignColor($parsedBody['design_color']); diff --git a/server/src/Entity/Instance.php b/server/src/Entity/Instance.php index 044e326..bfc1546 100644 --- a/server/src/Entity/Instance.php +++ b/server/src/Entity/Instance.php @@ -59,6 +59,13 @@ class Instance implements \JsonSerializable */ protected $dataPath; + /** + * @var string + * + * @Column(type="string", name="files_path", nullable=true) + */ + protected $filesPath; + /** * @var bool * @@ -230,6 +237,16 @@ class Instance implements \JsonSerializable $this->dataPath = $dataPath; } + public function getFilesPath() + { + return $this->filesPath; + } + + public function setFilesPath($filesPath) + { + $this->filesPath = $filesPath; + } + public function getPublic() { return $this->public; @@ -412,6 +429,7 @@ class Instance implements \JsonSerializable 'description' => $this->getDescription(), 'display' => $this->getDisplay(), 'data_path' => $this->getDataPath(), + 'files_path' => $this->getFilesPath(), 'public' => $this->getPublic(), 'portal_logo' => $this->getPortalLogo(), 'design_color' => $this->getDesignColor(), diff --git a/server/tests/Action/InstanceActionTest.php b/server/tests/Action/InstanceActionTest.php index 2b79c1c..1340c77 100644 --- a/server/tests/Action/InstanceActionTest.php +++ b/server/tests/Action/InstanceActionTest.php @@ -82,6 +82,7 @@ final class InstanceActionTest extends TestCase 'description' => 'Test', 'display' => 10, 'data_path' => '/DEFAULT', + 'files_path' => '/INSTANCE_FILES', 'public' => true, 'portal_logo' => '', 'design_color' => '#7AC29A', diff --git a/server/tests/Action/InstanceListActionTest.php b/server/tests/Action/InstanceListActionTest.php index d263fcd..2254c0b 100644 --- a/server/tests/Action/InstanceListActionTest.php +++ b/server/tests/Action/InstanceListActionTest.php @@ -81,6 +81,7 @@ final class InstanceListActionTest extends TestCase 'description' => 'Test', 'display' => 10, 'data_path' => '/DEFAULT', + 'files_path' => '/INSTANCE_FILES', 'public' => true, 'portal_logo' => '', 'design_color' => '#7AC29A', -- GitLab From f05c475af6896dc9ce252056186ba41bf2055535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= Date: Fri, 1 Apr 2022 11:51:05 +0200 Subject: [PATCH 21/59] Create containers for dataset images --- .../dataset/dataset-card.component.html | 4 + .../dataset/dataset-form.component.html | 18 ----- .../dataset/dataset-form.component.ts | 10 --- .../components/image/image-form.component.ts | 7 +- .../image/image-table.component.html | 60 +-------------- .../components/image/image-table.component.ts | 38 +--------- .../containers/edit-dataset.component.html | 10 --- .../containers/edit-dataset.component.ts | 36 +-------- .../containers/edit-image.component.html | 47 ++++++++++++ .../containers/edit-image.component.ts | 76 +++++++++++++++++++ .../containers/image-list.component.html | 38 ++++++++++ .../containers/image-list.component.ts | 51 +++++++++++++ .../containers/new-image.component.html | 46 +++++++++++ .../dataset/containers/new-image.component.ts | 65 ++++++++++++++++ .../dataset/dataset-routing.module.ts | 11 ++- .../app/metamodel/effects/image.effects.ts | 21 ++++- .../app/metamodel/selectors/image.selector.ts | 6 ++ 17 files changed, 375 insertions(+), 169 deletions(-) create mode 100644 client/src/app/admin/instance/dataset/containers/edit-image.component.html create mode 100644 client/src/app/admin/instance/dataset/containers/edit-image.component.ts create mode 100644 client/src/app/admin/instance/dataset/containers/image-list.component.html create mode 100644 client/src/app/admin/instance/dataset/containers/image-list.component.ts create mode 100644 client/src/app/admin/instance/dataset/containers/new-image.component.html create mode 100644 client/src/app/admin/instance/dataset/containers/new-image.component.ts diff --git a/client/src/app/admin/instance/dataset/components/dataset/dataset-card.component.html b/client/src/app/admin/instance/dataset/components/dataset/dataset-card.component.html index 7ea2e12..0c757a8 100644 --- a/client/src/app/admin/instance/dataset/components/dataset/dataset-card.component.html +++ b/client/src/app/admin/instance/dataset/components/dataset/dataset-card.component.html @@ -17,6 +17,10 @@   +