Skip to content
Snippets Groups Projects
Commit d7d6861d authored by Tifenn Guillas's avatar Tifenn Guillas
Browse files

Merge branch 'develop' into 10-add-tests-for-metamodel-module

parents 782e757e ecc4f6ca
No related branches found
No related tags found
2 merge requests!72Develop,!27Resolve "Add tests for metamodel module"
Showing
with 194 additions and 58 deletions
<div *ngIf="getDataset().config.datatable.datatable_selectable_rows" class="btn-group mb-2" dropdown [isDisabled]="selectedData.length < 1">
<button id="button-basic" dropdownToggle type="button" class="btn btn-primary dropdown-toggle" aria-controls="dropdown-basic">
Actions <span class="caret"></span>
</button>
<ul id="dropdown-basic" *dropdownMenu class="dropdown-menu" role="menu" aria-labelledby="button-basic">
<li *ngIf="getConfigDownloadResultFormat('download_csv')" role="menuitem">
<a class="dropdown-item" [href]="getUrl('csv')" (click)="click($event, getUrl('csv'), 'csv')">
<span class="fas fa-file-csv"></span> Download CSV
</a>
</li>
<li *ngIf="getConfigDownloadResultFormat('download_ascii')" role="menuitem">
<a class="dropdown-item" [href]="getUrl('ascii')" (click)="click($event, getUrl('ascii'), 'txt')">
<span class="fas fa-file"></span> Download ASCII
</a>
</li>
<li *ngIf="getConfigDownloadResultFormat('download_vo')" role="menuitem">
<a class="dropdown-item" [href]="getUrl('votable')" (click)="click($event, getUrl('votable'), 'xml')">
<span class="fas fa-file"></span> VOtable
</a>
</li>
<li *ngIf="getConfigDownloadResultFormat('download_vo')" role="menuitem">
<a class="dropdown-item" (click)="broadcastVotable()">
<span class="fas fa-broadcast-tower"></span> Broadcast VOtable
</a>
</li>
<li *ngIf="getConfigDownloadResultFormat('download_archive')" role="menuitem">
<a class="dropdown-item" [href]="getUrlArchive()" (click)="click($event, getUrlArchive(), 'zip')">
<span class="fas fa-archive"></span> Download files archive
</a>
</li>
</ul>
</div>
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { Dataset, Attribute } from 'src/app/metamodel/models';
import { Criterion, ConeSearch, criterionToString } from 'src/app/instance/store/models';
import { getHost } from 'src/app/shared/utils';
import { AppConfigService } from 'src/app/app-config.service';
@Component({
selector: 'app-datatable-actions',
templateUrl: 'datatable-actions.component.html'
})
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() broadcast: EventEmitter<string> = new EventEmitter();
constructor(private appConfig: AppConfigService, private http: HttpClient, private toastr: ToastrService) { }
/**
* Checks if the download format is allowed by Anis Admin configuration.
*
* @param {string} format - The file format to download.
*
* @return boolean
*/
getConfigDownloadResultFormat(format: string): boolean {
const dataset = this.getDataset();
return dataset.config.download[format];
}
getDataset() {
return this.datasetList.find(d => d.name === this.datasetSelected);
}
/**
* Returns URL to download file for the given format.
*
* @param {string} format - The file format to download.
*
* @return string
*/
getUrl(format: string): string {
let query: string = `${getHost(this.appConfig.apiUrl)}/search/${this.datasetSelected}?a=${this.outputList.join(';')}`;
if (this.criteriaList.length > 0) {
query += `&c=${this.criteriaList.map(criterion => criterionToString(criterion)).join(';')};${this.getCriterionSelectedData()}`;
} else {
query += `&c=${this.getCriterionSelectedData()}`;
}
if (this.coneSearch) {
query += `&cs=${this.coneSearch.ra}:${this.coneSearch.dec}:${this.coneSearch.radius}`;
}
query += `&f=${format}`;
return query;
}
getCriterionSelectedData() {
const attributeId = this.attributeList.find(a => a.search_flag === 'ID');
return `${attributeId.id}::in::${this.selectedData.join('|')}`;
}
/**
* Returns URL to download archive.
*
* @return boolean
*/
getUrlArchive(): string {
let query: string = `${getHost(this.appConfig.apiUrl)}/archive/${this.datasetSelected}?a=${this.outputList.join(';')}`;
if (this.criteriaList.length > 0) {
query += `&c=${this.criteriaList.map(criterion => criterionToString(criterion)).join(';')};${this.getCriterionSelectedData()}`;
} else {
query += `&c=${this.getCriterionSelectedData()}`;
}
if (this.coneSearch) {
query += `&cs=${this.coneSearch.ra}:${this.coneSearch.dec}:${this.coneSearch.radius}`;
}
return query;
}
/**
* Emits event to action to broadcast data.
*
* @fires EventEmitter<string>
*/
broadcastVotable(): void {
this.broadcast.emit(this.getUrl('votable'));
}
/**
* Allows to download file.
*/
click(event, href, extension): void {
event.preventDefault();
if (extension === 'zip') {
this.toastr.info('Achive is under construction, please wait', 'Download archive');
}
this.http.get(href, {responseType: "blob"}).subscribe(
data => {
let downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(data);
downloadLink.setAttribute('download', `${this.datasetSelected}.${extension}`);
downloadLink.click();
}
);
}
}
......@@ -8,6 +8,18 @@
<span *ngIf="!ag.isOpen"><span class="fas fa-chevron-down"></span></span>
</span>
</button>
<app-datatable-actions
[selectedData]="selectedData"
[datasetSelected]="datasetSelected"
[datasetList]="datasetList"
[attributeList]="attributeList"
[criteriaList]="criteriaList"
[outputList]="outputList"
[coneSearch]="coneSearch"
[dataLength]="dataLength"
[sampRegistered]="sampRegistered"
(broadcast)="broadcast.emit($event)">
</app-datatable-actions>
<app-datatable
[dataset]="datasetList | datasetByName:datasetSelected"
[instance]="instance"
......
......@@ -6,7 +6,7 @@ import { AccordionModule } from 'ngx-bootstrap/accordion';
import { DatatableTabComponent } from './datatable-tab.component';
import { Attribute, Dataset, Instance } from '../../../../metamodel/models';
import { SearchQueryParams } from '../../../store/models';
import { SearchQueryParams, Criterion, ConeSearch } from '../../../store/models';
import { DatasetByNamePipe } from '../../../../shared/pipes/dataset-by-name.pipe';
describe('[Instance][Search][Component][Result] DatatableTabComponent', () => {
......@@ -24,6 +24,19 @@ describe('[Instance][Search][Component][Result] DatatableTabComponent', () => {
@Input() selectedData: any[] = [];
}
@Component({ selector: 'app-datatable-actions', template: '' })
class DatatableActionsStubComponent {
@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;
}
let component: DatatableTabComponent;
let fixture: ComponentFixture<DatatableTabComponent>;
......@@ -32,6 +45,7 @@ describe('[Instance][Search][Component][Result] DatatableTabComponent', () => {
declarations: [
DatatableTabComponent,
DatatableStubComponent,
DatatableActionsStubComponent,
DatasetByNamePipe
],
imports: [
......
......@@ -10,7 +10,7 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { Instance, Attribute, Dataset } from 'src/app/metamodel/models';
import { Pagination, SearchQueryParams } from 'src/app/instance/store/models';
import { Pagination, SearchQueryParams, Criterion, ConeSearch } from 'src/app/instance/store/models';
/**
* @class
......@@ -27,8 +27,11 @@ export class DatatableTabComponent {
@Input() datasetList: Dataset[];
@Input() attributeList: Attribute[];
@Input() outputList: number[];
@Input() criteriaList: Criterion[];
@Input() coneSearch: ConeSearch;
@Input() queryParams: SearchQueryParams;
@Input() dataLength: number;
@Input() sampRegistered: boolean;
@Input() data: any[];
@Input() dataIsLoading: boolean;
@Input() dataIsLoaded: boolean;
......@@ -36,4 +39,5 @@ export class DatatableTabComponent {
@Output() retrieveData: EventEmitter<Pagination> = new EventEmitter();
@Output() addSelectedData: EventEmitter<number | string> = new EventEmitter();
@Output() deleteSelectedData: EventEmitter<number | string> = new EventEmitter();
@Output() broadcast: EventEmitter<string> = new EventEmitter();
}
......@@ -4,6 +4,7 @@ import { ReminderComponent } from './reminder.component';
import { SampComponent } from './samp.component';
import { UrlDisplayComponent } from './url-display.component';
import { DatatableComponent } from './datatable.component';
import { DatatableActionsComponent } from './datatable-actions.component';
import { rendererComponents } from './renderer';
export const resultComponents = [
......@@ -13,5 +14,6 @@ export const resultComponents = [
SampComponent,
UrlDisplayComponent,
DatatableComponent,
DatatableActionsComponent,
rendererComponents
];
......@@ -65,16 +65,19 @@
[instance]="instance | async"
[datasetList]="datasetList | async"
[attributeList]="attributeList | async | sortByOutputDisplay"
[criteriaList]="criteriaList | async"
[outputList]="outputList | async"
[queryParams]="queryParams | async"
[dataLength]="dataLength | async"
[sampRegistered]="sampRegistered | async"
[data]="data | async"
[dataIsLoading]="dataIsLoading | async"
[dataIsLoaded]="dataIsLoaded | async"
[selectedData]="selectedData | async"
(retrieveData)="retrieveData($event)"
(addSelectedData)="addSearchData($event)"
(deleteSelectedData)="deleteSearchData($event)">
(deleteSelectedData)="deleteSearchData($event)"
(broadcast)="broadcastVotable($event)">
</app-datatable-tab>
</ng-container>
</div>
......
......@@ -66,8 +66,11 @@ describe('[Instance][Search][Container] ResultComponent', () => {
@Input() datasetList: Dataset[];
@Input() attributeList: Attribute[];
@Input() outputList: number[];
@Input() criteriaList: Criterion[];
@Input() coneSearch: ConeSearch;
@Input() queryParams: SearchQueryParams;
@Input() dataLength: number;
@Input() sampRegistered: boolean;
@Input() data: any[];
@Input() dataIsLoading: boolean;
@Input() dataIsLoaded: boolean;
......
......@@ -55,6 +55,7 @@ import { sharedPipes } from './pipes';
CommonModule,
FormsModule,
ReactiveFormsModule,
BsDropdownModule,
ModalModule,
AccordionModule,
PopoverModule,
......
......@@ -203,7 +203,7 @@ $container->set('App\Action\SearchAction', function (ContainerInterface $c) {
$container->set('App\Action\ArchiveAction', function (ContainerInterface $c) {
$anisQueryBuilder = (new App\Search\Query\AnisQueryBuilder())
->addQueryPart(new App\Search\Query\From())
->addQueryPart(new App\Search\Query\SelectFile())
->addQueryPart(new App\Search\Query\Select())
->addQueryPart(new App\Search\Query\ConeSearch())
->addQueryPart(new App\Search\Query\Where(new App\Search\Query\Operator\OperatorFactory()))
->addQueryPart(new App\Search\Query\Order())
......
......@@ -150,8 +150,8 @@ final class ArchiveAction extends AbstractAction
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
foreach ($attributesSelected as $attribute) {
$attributeLabel = $attribute->getLabel();
$filePath = $this->dataPath . $dataset->getDataPath() . DIRECTORY_SEPARATOR . $row[$attributeLabel];
if (file_exists($filePath)) {
$filePath = $this->dataPath . $dataset->getFullDataPath() . DIRECTORY_SEPARATOR . $row[$attributeLabel];
if (file_exists($filePath) && is_file($filePath)) {
$zip->addFile($filePath, $row[$attributeLabel]);
}
}
......
<?php
/*
* This file is part of Anis Server.
*
* (c) 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.
*/
declare(strict_types=1);
namespace App\Search\Query;
use App\Entity\Dataset;
/**
* Represents the Anis Select File attributes Query Part
*
* @author François Agneray <francois.agneray@lam.fr>
* @package App\Search\Query
*/
class SelectFile extends AbstractQueryPart
{
/**
* Adds the select clause to the request and set only attribute with search_flag = FILE
*
* @param AnisQueryBuilder $anisQueryBuilder Represents the query being built
* @param Dataset $dataset Represents the requested dataset
* @param string[] $queryParams The query params of the url (after ?)
*/
public function __invoke(AnisQueryBuilder $anisQueryBuilder, Dataset $dataset, array $queryParams): void
{
if ($queryParams['a'] === 'all') {
$listOfIds = array_map(fn($attribute) => $attribute->getId(), $dataset->getAttributes());
} else {
$listOfIds = explode(';', $queryParams['a']);
}
$columns = array();
$attributes = array();
foreach ($listOfIds as $id) {
$attribute = $this->getAttribute($dataset, (int) $id);
if ($attribute->getSearchFlag() === 'FILE') {
$columns[] = $dataset->getTableRef() . '.' . $attribute->getName() . ' as ' . $attribute->getLabel();
$attributes[] = $attribute;
}
}
$anisQueryBuilder->getDoctrineQueryBuilder()->select($columns);
$anisQueryBuilder->setAttributesSelected($attributes);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment