Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/download tab #1635

Merged
merged 30 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
90bf895
move download component to separate tab
lukavdplas Jul 4, 2024
51d63e0
add total results class
lukavdplas Jul 4, 2024
f8ae8ec
fetch total results in download tab
lukavdplas Jul 4, 2024
593ad13
add search tab class
lukavdplas Jul 4, 2024
665bb3d
add search tabs class to search component
lukavdplas Jul 4, 2024
22d102e
fix search tabs class
lukavdplas Jul 4, 2024
abb24fd
scaffold download form
lukavdplas Jul 4, 2024
a3976d6
add URL to results instead of download page
lukavdplas Jul 4, 2024
31e3268
pick encoding directly from download form
lukavdplas Jul 4, 2024
37de485
remove passing resultoverview
lukavdplas Jul 4, 2024
89b60f6
remove searched event
lukavdplas Jul 4, 2024
d04d2b1
move sort config into separate module
lukavdplas Jul 4, 2024
9524779
configure sort in download component
lukavdplas Jul 4, 2024
fd277b0
add query in context to download form
lukavdplas Jul 4, 2024
7318803
add test for request with query in context
lukavdplas Jul 4, 2024
3b58238
hide query in context option
lukavdplas Jul 18, 2024
2304cd0
set button type in sort component
lukavdplas Jul 4, 2024
f0c310a
use multiselect component for field selection
lukavdplas Jul 4, 2024
7a00e49
simplify download service calls
lukavdplas Jul 18, 2024
e3943e5
fix download history error when download includes context column
lukavdplas Jul 18, 2024
8cc2f25
update spec
lukavdplas Jul 18, 2024
7af5cbd
download form clarity + accessibility
lukavdplas Jul 19, 2024
986f7be
add 'tags' and 'context' to forbidden field names
lukavdplas Jul 19, 2024
9da43a7
fix error when current user is null
lukavdplas Jul 19, 2024
799e745
correct check for long download
lukavdplas Jul 19, 2024
a7b8281
correct pytest mark
lukavdplas Jul 19, 2024
874d500
add "tab" to forbidden field names
lukavdplas Jul 19, 2024
ea012f8
use single const for default highlight size
lukavdplas Jul 22, 2024
4275ec1
add return types
lukavdplas Jul 22, 2024
b5fbd5d
Merge branch 'develop' into feature/download-tab
lukavdplas Jul 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions backend/addcorpus/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,18 @@ class VisualizationType(Enum):
'scan',
'tab-scan'
'p',
'tags',
'context',
'tab',
]
'''
Field names that cannot be used because they are also query parameters in frontend routes.
Field names that cannot be used because they interfere with other functionality.

Using them would make routing ambiguous.
This is usually because they are also query parameters in frontend routes, and using them
would make routing ambiguous.

`query` is also forbidden because it is a reserved column in CSV downloads. Likewise,
`context` is forbidden because it's used in download requests.

`scan` and `tab-scan` are added because they interfere with element IDs in the DOM.
'''
19 changes: 19 additions & 0 deletions backend/download/tests/test_download_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,22 @@ def test_query_text_in_csv(db, client, basic_mock_corpus, basic_corpus_public, i
reader = csv.DictReader(stream, delimiter=';')
row = next(reader)
assert row['query'] == 'ghost'

@pytest.mark.xfail(reason='query in context download does not work')
def test_download_with_query_in_context(
db, admin_client, small_mock_corpus, index_small_mock_corpus
):
es_query = query.set_query_text(query.MATCH_ALL, 'the')
es_query['highlight'] = { 'fragment_size': 200, 'fields': { 'content': {} } }
es_query['size'] = 3
request_json = {
'corpus': small_mock_corpus,
'es_query': es_query,
'fields': ['date', 'content', 'context'],
'route': f"/search/{small_mock_corpus}?query=the&highlight=200",
'encoding': 'utf-8'
}
response = admin_client.post(
'/api/download/search_results', request_json, content_type='application/json'
)
assert status.is_success(response.status_code)
115 changes: 96 additions & 19 deletions frontend/src/app/download/download.component.html
Original file line number Diff line number Diff line change
@@ -1,22 +1,99 @@
<div class="level-item">
<div class="field has-addons">
<p class="control" iaBalloon="Only the first {{downloadLimit || directDownloadLimit}} results will be in the file!"
[iaBalloonVisible]="hasLimitedResults ? undefined : false" iaBalloonLength="fit">
<button class="button is-primary" [ngClass]="{'is-loading':isDownloading}" type="download"
(click)="chooseDownloadMethod()" [disabled]="downloadDisabled">
<span class="icon">
<fa-icon [icon]="actionIcons.download" aria-hidden="true"></fa-icon>
</span>
<span>Download csv</span>
</button>
</p>
<p class="control" iaBalloon="Click here to select which fields should appear in the csv" iaBalloonLength="medium">
<ia-select-field [corpusFields]="availableCsvFields" filterCriterion="downloadable"
(selection)="selectCsvFields($event)">
</ia-select-field>
<ng-container *ngIf="totalResults.result$ | async as total">
<p class="block" aria-live="polite" aria-atomic="true">{{total}} results.</p>

<p class="block">
You can download your search results as a CSV file. View the
<a [routerLink]="['/manual', 'download']">manual</a>
for more information.
</p>

<div class="message" *ngIf="downloadLimit < total">
<p class="message-body">
Only the first {{downloadLimit}} results will be included in the file.
</p>
</div>
</div>
<form>
<div class="field">
<label class="label" id="fields-select-label">Fields</label>
<div class="control">
<p-multiSelect [options]="availableCsvFields"
[(ngModel)]="selectedCsvFields"
optionLabel="displayName"
placeholder="select fields"
name="searchFields"
ariaLabelledBy="fields-select-label">
</p-multiSelect>
</div>
<p class="help">
Select which fields should be included as columns in the CSV file.
</p>
</div>

<div class="field">
<p class="label">
Sort results
</p>
<ia-search-sorting [pageResults]="resultsConfig"></ia-search-sorting>
</div>

<!-- TODO: show this option when query-in-context download is fixed -->
<div class="field" *ngIf="queryModel.queryText" hidden>
<div role="group" class="control">
<legend class="label">
Additional columns
</legend>
<label>
<!-- checkbox: include query? -->
<!-- checkbox: include user tags? -->
<input type="checkbox"
[checked]="(resultsConfig.highlight$ | async) !== undefined"
(change)="onHighlightChange($event)">
Include "query in context" snippets
</label>
</div>
</div>

<ia-download-options [download]="pendingDownload" [isDownloading]="isDownloading" (cancel)="pendingDownload = undefined"
(confirm)="confirmDirectDownload($event)"></ia-download-options>
<ng-container *ngIf="(canDownloadDirectly$ | async); else longDownloadSubmit">
<div class="field">
<div role="group" class="control">
<legend class="label">File encoding</legend>
<label class="radio" *ngFor="let encodingOption of encodingOptions">
<input type="radio" name="encoding" (click)="encoding=encodingOption" [checked]="encoding===encodingOption">
{{encodingOption}}
</label>
</div>
<p class="help">
We recommend using utf-8 encoding for most applications, including Python and R.
For importing files in Microsoft Excel, we recommend utf-16.
</p>
</div>
<div class="block">
<button class="button is-primary" [ngClass]="{'is-loading':isDownloading}"
type="submit"
(click)="confirmDirectDownload()" [disabled]="total == 0">
<span class="icon">
<fa-icon [icon]="actionIcons.download" aria-hidden="true"></fa-icon>
</span>
<span>Download</span>
</button>
</div>
</ng-container>
<ng-template #longDownloadSubmit>
<div class="message">
<p class="message-body">
Your download contains too many documents to be immediately available.
You can request the download now, and receive an email when it's
ready.
</p>
</div>
<div class="block">
<button class="button is-primary" type="submit" (click)="longDownload()">
<span class="icon">
<fa-icon [icon]="actionIcons.wait"></fa-icon>
</span>
<span>Request download</span>
</button>
</div>
</ng-template>
</form>
</ng-container>
18 changes: 10 additions & 8 deletions frontend/src/app/download/download.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { commonTestBed } from '../common-test-bed';
import { QueryModel } from '../models';

import { DownloadComponent } from './download.component';
import { SimpleChange } from '@angular/core';

describe('DownloadComponent', () => {
let component: DownloadComponent;
Expand All @@ -19,7 +20,9 @@ describe('DownloadComponent', () => {
component = fixture.componentInstance;
component.corpus = mockCorpus;
component.queryModel = new QueryModel(mockCorpus);
component.ngOnChanges();
component.ngOnChanges({
queryModel: new SimpleChange(undefined, component.queryModel, true)
});
fixture.detectChanges();
});

Expand All @@ -29,16 +32,15 @@ describe('DownloadComponent', () => {

it('should respond to field selection', () => {
// Start with a single field
expect(component['getCsvFields']()).toEqual(mockCorpus.fields);
expect(component['getColumnNames']()).toEqual(['great_field', 'speech']);

// Deselect all
component.selectCsvFields([]);
expect(component['getCsvFields']()).toEqual([]);
component.selectedCsvFields = [];
expect(component['getColumnNames']()).toEqual([]);

// Select two
component.selectCsvFields([mockField, mockField2]);
const expected_fields = [mockField, mockField2];
expect(component['getCsvFields']()).toEqual(expected_fields);
expect(component.selectedCsvFields).toEqual(expected_fields);
component.selectedCsvFields = [mockField, mockField2];
const expected_fields = ['great_field', 'speech'];
expect(component['getColumnNames']()).toEqual(expected_fields);
});
});
Loading
Loading