Skip to content

Commit 8b4c454

Browse files
authored
Merge pull request #24 from cmu-delphi/development
Development
2 parents eefcfd6 + 1534ae7 commit 8b4c454

18 files changed

+650
-259
lines changed

src/assets/css/custom_styles.css

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,135 @@
3030

3131
.text-center {
3232
text-align: center !important;
33+
}
34+
35+
.code-block {
36+
background-color: #f4f4f4;
37+
border: 1px solid #ddd;
38+
border-radius: 4px;
39+
padding: 15px;
40+
font-family: 'Courier New', Courier, monospace;
41+
font-size: 14px;
42+
line-height: 1.6;
43+
overflow-x: auto;
44+
white-space: pre-wrap;
45+
margin: 10px 0;
46+
}
47+
48+
.highlight-code {
49+
font-size: 0.875em!important;
50+
color: var(--mdb-code-color)!important;
51+
word-wrap: break-word!important;
52+
}
53+
54+
55+
56+
/* Loader Overlay */
57+
.loader-overlay {
58+
position: fixed;
59+
top: 0;
60+
left: 0;
61+
width: 100%;
62+
height: 100%;
63+
background: rgba(0, 0, 0, 0.5); /* Semi-transparent black overlay */
64+
display: none; /* Hidden by default */
65+
justify-content: center;
66+
align-items: center;
67+
z-index: 1000; /* Ensure it’s on top */
68+
}
69+
70+
/* Table fade effect when loader is active */
71+
.table-container.faded {
72+
opacity: 0.3; /* Fades the table */
73+
transition: opacity 0.3s ease;
74+
}
75+
76+
/* lds-roller CSS from loading.io */
77+
.lds-roller {
78+
display: inline-block;
79+
position: relative;
80+
width: 200px;
81+
height: 200px;
82+
}
83+
.lds-roller div {
84+
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
85+
transform-origin: 40px 40px;
86+
}
87+
.lds-roller div:after {
88+
content: " ";
89+
display: block;
90+
position: absolute;
91+
width: 7px;
92+
height: 7px;
93+
border-radius: 50%;
94+
background: #fff;
95+
margin: -4px 0 0 -4px;
96+
}
97+
.lds-roller div:nth-child(1) {
98+
animation-delay: -0.036s;
99+
}
100+
.lds-roller div:nth-child(1):after {
101+
top: 63px;
102+
left: 63px;
103+
}
104+
.lds-roller div:nth-child(2) {
105+
animation-delay: -0.072s;
106+
}
107+
.lds-roller div:nth-child(2):after {
108+
top: 68px;
109+
left: 56px;
110+
}
111+
.lds-roller div:nth-child(3) {
112+
animation-delay: -0.108s;
113+
}
114+
.lds-roller div:nth-child(3):after {
115+
top: 71px;
116+
left: 48px;
117+
}
118+
.lds-roller div:nth-child(4) {
119+
animation-delay: -0.144s;
120+
}
121+
.lds-roller div:nth-child(4):after {
122+
top: 72px;
123+
left: 40px;
124+
}
125+
.lds-roller div:nth-child(5) {
126+
animation-delay: -0.18s;
127+
}
128+
.lds-roller div:nth-child(5):after {
129+
top: 71px;
130+
left: 32px;
131+
}
132+
.lds-roller div:nth-child(6) {
133+
animation-delay: -0.216s;
134+
}
135+
.lds-roller div:nth-child(6):after {
136+
top: 68px;
137+
left: 24px;
138+
}
139+
.lds-roller div:nth-child(7) {
140+
animation-delay: -0.252s;
141+
}
142+
.lds-roller div:nth-child(7):after {
143+
top: 63px;
144+
left: 17px;
145+
}
146+
.lds-roller div:nth-child(8) {
147+
animation-delay: -0.288s;
148+
}
149+
.lds-roller div:nth-child(8):after {
150+
top: 56px;
151+
left: 12px;
152+
}
153+
@keyframes lds-roller {
154+
0% {
155+
transform: rotate(0deg);
156+
}
157+
100% {
158+
transform: rotate(360deg);
159+
}
160+
}
161+
162+
.additional-filters-button {
163+
width: 100%;
33164
}

src/assets/js/indicatorHandler.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,44 @@ class IndicatorHandler {
273273
$('#modeSubmitResult').html(JSON.stringify(data, null, 2));
274274
});
275275
}
276+
277+
createQueryCode() {
278+
279+
var fluviewRegions = $("#fluviewRegions").select2("data");
280+
281+
var covidCastGeographicValues = Object.groupBy(
282+
$("#geographic_value").select2("data"),
283+
({ geoType }) => [geoType]
284+
);
285+
286+
const submitData = {
287+
start_date: document.getElementById("start_date").value,
288+
end_date: document.getElementById("end_date").value,
289+
indicators: this.indicators,
290+
covidCastGeographicValues: covidCastGeographicValues,
291+
fluviewRegions: fluviewRegions,
292+
}
293+
const csrftoken = Cookies.get("csrftoken");
294+
var createQueryCodePython = `<h4>PYTHON PACKAGE</h4>`
295+
+ `<p>Install <code class="highlight-code">covidcast</code> via pip: </p>`
296+
+ `<pre class="code-block"><code>pip install covidcast</code></pre><br>`
297+
+ `<p>Fetch data: </p>`;
298+
var createQueryCodeR = `<h4>R PACKAGE</h4>`
299+
+ `<p>Install <code class="highlight-code">covidcast</code> via CRAN: </p>`
300+
+ `<pre class="code-block"><code>install.packages('covidcast')</code></pre><br>`
301+
+ `<p> Fetch data: </p>`
302+
$.ajax({
303+
url: "create_query_code/",
304+
type: "POST",
305+
dataType: "json",
306+
contentType: "application/json",
307+
headers: { "X-CSRFToken": csrftoken },
308+
data: JSON.stringify(submitData),
309+
}).done(function (data) {
310+
createQueryCodePython += data["python_code_blocks"].join("<br>");
311+
createQueryCodeR += data["r_code_blocks"].join("<br>");
312+
$('#modeSubmitResult').html(createQueryCodePython+"<br>"+createQueryCodeR);
313+
});
314+
315+
}
276316
}

src/assets/js/indicatorSetsFilters.js

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,50 @@
11
// Add an event listener to each 'bulk-select' element
22
let bulkSelectDivs = document.querySelectorAll('.bulk-select');
33
bulkSelectDivs.forEach(div => {
4-
div.addEventListener('click', function(event) {
4+
div.addEventListener('click', function (event) {
55
let form = this.nextElementSibling;
6-
let showMoreLink = form.querySelector('a');
76
let checkboxes = form.querySelectorAll('input[type="checkbox"]');
87

98
if (event.target.checked === true) {
10-
checkboxes.forEach((checkbox, index) => {
9+
checkboxes.forEach((checkbox) => {
1110
checkbox.checked = true;
12-
if (index > 4) {
13-
checkbox.parentElement.style.display = checkbox.parentElement.style.display === 'none' ? 'block' : null;
14-
}
1511
})
16-
if (showMoreLink) {
17-
showMoreLink.innerText = 'Show less...';
18-
}
1912
} else if (event.target.checked === false) {
20-
checkboxes.forEach((checkbox, index) => {
13+
checkboxes.forEach((checkbox) => {
2114
checkbox.checked = false
22-
if (index > 4) {
23-
checkbox.parentElement.style.display = checkbox.parentElement.style.display === 'block' ? 'none' : null;
24-
}
2515
});
26-
if (showMoreLink) {
27-
showMoreLink.innerText = 'Show more...';
28-
}
2916
}
3017
});
18+
});
19+
20+
function checkBulkSelect() {
21+
bulkSelectDivs.forEach((div) => {
22+
let form = div.nextElementSibling;
23+
let checkboxes = form.querySelectorAll("input[type='checkbox']");
24+
let allChecked = Array.from(checkboxes).every(cb => cb.checked);
25+
if (allChecked) {
26+
div.querySelector('#select-all').checked = true;
27+
}
28+
});
29+
}
30+
31+
checkBulkSelect();
32+
33+
function showLoader() {
34+
document.getElementById('loaderOverlay').style.display = 'flex';
35+
document.querySelector('.table-container').classList.add('faded');
36+
}
37+
38+
$("#filterIndicatorSetsForm").find("input[type='checkbox']").on("change", function (e) {
39+
// Show loader and fade table
40+
showLoader();
41+
this.form.submit();
42+
});
43+
44+
$("#location_search").on({
45+
"change": function (e) {
46+
// Show loader and fade table
47+
showLoader();
48+
this.form.submit();
49+
}
3150
});

src/assets/js/indicatorSetsTable.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,22 @@ var table = new DataTable("#indicatorSetsTable", {
2525
mark: true,
2626

2727
language: {
28-
buttons: {
29-
colvis: "Toggle Columns",
30-
},
28+
emptyTable: "No indicators match your specified filters. Try relaxing some filters, or clear all filters and try again.",
29+
// buttons: {
30+
// colvis: "Toggle Columns",
31+
// },
3132
},
3233
});
3334

34-
new DataTable.Buttons(table, {
35-
buttons: [
36-
{
37-
extend: "colvis",
38-
columns: "th:nth-child(n+3)",
39-
prefixButtons: ["colvisRestore"],
40-
},
41-
],
42-
});
35+
// new DataTable.Buttons(table, {
36+
// buttons: [
37+
// {
38+
// extend: "colvis",
39+
// columns: "th:nth-child(n+3)",
40+
// prefixButtons: ["colvisRestore"],
41+
// },
42+
// ],
43+
// });
4344

4445
table.buttons(0, null).container().appendTo("#colvis");
4546

src/assets/js/selectedIndicatorsModal.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,16 @@ function handleModeChange(mode) {
104104
el.style.display = 'flex';
105105
});
106106
$('#modeSubmitResult').html('');
107-
} else {
107+
} else if (mode === 'preview') {
108108
currentMode = 'preview';
109109
choose_dates.forEach((el) => {
110110
el.style.display = 'flex';
111111
});
112+
} else if (mode === 'create_query_code') {
113+
currentMode = 'create_query_code'
114+
choose_dates.forEach((el) => {
115+
el.style.display = 'flex';
116+
});
112117
}
113118
document.getElementsByName("modes").forEach((el) => {
114119
if (currentMode === el.value) {
@@ -204,7 +209,14 @@ $("#showSelectedIndicatorsButton").click(function () {
204209
$("#differentLocationNote").html(otherEndpointLocationsWarning)
205210
if (document.getElementsByName("fluviewRegions").length === 0) {
206211
indicatorHandler.showFluviewRegions();
212+
} else {
213+
// IF code goes here, we assume that otherEndpointLocationWarning & fluviewRegion selector is already on the page, but is just hidden, so we should just show it.
214+
$("#otherEndpointLocationsWrapper").show();
207215
}
216+
} else {
217+
// If there are no non-covidcast indicators selected (only fluview is supported for now) then hide otherEndpointLocationWarning & fliviewRegions selector.
218+
$("#fluviewRegions").val(null).trigger("change");
219+
$("#otherEndpointLocationsWrapper").hide();
208220
}
209221
});
210222

@@ -224,7 +236,9 @@ function submitMode(event) {
224236
indicatorHandler.plotData();
225237
} else if (currentMode === 'export') {
226238
indicatorHandler.exportData();
227-
} else {
239+
} else if (currentMode === 'preview') {
228240
indicatorHandler.previewData();
241+
} else if (currentMode === 'create_query_code') {
242+
indicatorHandler.createQueryCode();
229243
}
230244
}

src/fixtures/column_descriptions.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"model": "indicatorsets.columndescription", "pk": 18, "fields": {"name": "name", "description": "Hover over the indicator's name to see a brief description."}}, {"model": "indicatorsets.columndescription", "pk": 19, "fields": {"name": "geographic_coverage", "description": "The countries or world regions covered by this indicator. These are typically covered at finer geographic levels / jurisdictions."}}, {"model": "indicatorsets.columndescription", "pk": 20, "fields": {"name": "geographic_levels", "description": "All the geographic levels at which this indicator is available. Larger jurisdictions are often based on aggregation of data from constituent jurisdictions."}}, {"model": "indicatorsets.columndescription", "pk": 21, "fields": {"name": "temporal_scope_start", "description": "The earliest date for which this indicator is available."}}, {"model": "indicatorsets.columndescription", "pk": 22, "fields": {"name": "temporal_scope_end", "description": "The latest date for which this indicator is available."}}, {"model": "indicatorsets.columndescription", "pk": 23, "fields": {"name": "temporal_granularity", "description": "The temporal resolution of this indicator (not of the reporting). Might not be the same as Reporting Cadence (e.g. a daily indicator may be reported only once a week)."}}, {"model": "indicatorsets.columndescription", "pk": 24, "fields": {"name": "reporting_cadence", "description": "The frequency with which this indicator is reported. This may be different from the temporal granularity."}}, {"model": "indicatorsets.columndescription", "pk": 25, "fields": {"name": "reporting_lag", "description": "The number of days from the last day of a reported period until the first reported value for that period is usually available in Delphi Epidata. E.g. if reporting U.S. epiweeks (Sunday through Saturday), and the first report is usually available in Delphi Epidata on the following Friday, The Reporting Lag is 6. By \"usually available\" we mean when it\\'s \"supposed to be\" available based on our current understanding of the data provider\\'s operations and Delphi\\'s ingestion pipeline. That is the date on which we think of the data as showing up \"on time\", and relative to which we track unusual delays."}}, {"model": "indicatorsets.columndescription", "pk": 26, "fields": {"name": "revision_cadende", "description": "How frequently are revised values (e.g. \"backfill\") usually reported (if any)?"}}, {"model": "indicatorsets.columndescription", "pk": 27, "fields": {"name": "population", "description": "The population or demographic group reflected by the indicator (\"All\" means the entire population)"}}, {"model": "indicatorsets.columndescription", "pk": 28, "fields": {"name": "population_stratifiers", "description": "What population or demographic stratifiers are available, if any?"}}, {"model": "indicatorsets.columndescription", "pk": 29, "fields": {"name": "surveillance_categories", "description": "Which surveillance categories or rungs in the Severity Pyramid does this indicator attempt to track? Some indicators may approximately track multiple categories."}}, {"model": "indicatorsets.columndescription", "pk": 30, "fields": {"name": "original_data_provider", "description": "The owner or supplier of the original or raw data used to create this indicator."}}, {"model": "indicatorsets.columndescription", "pk": 31, "fields": {"name": "pre_processing", "description": "Brief description of main data processing used in creating this set of indicators, including smoothing and aggregation. For more details, see the documentation."}}, {"model": "indicatorsets.columndescription", "pk": 32, "fields": {"name": "censoring", "description": "Is any of the data being censored (e.g. small counts)? If so how, and how much impact does it have (e.g. approximate fraction of values affected)."}}, {"model": "indicatorsets.columndescription", "pk": 33, "fields": {"name": "dua_required", "description": "Applicable data use terms (may apply even to publicly accessible indicators)."}}]

src/fixtures/filter_descriptions.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"model": "indicatorsets.filterdescription", "pk": 9, "fields": {"name": "pathogens", "description": "List only indicators related to these pathogens, syndromes or diseases."}}, {"model": "indicatorsets.filterdescription", "pk": 10, "fields": {"name": "geographic_scope", "description": "List only indicators that cover any of the selected countries or world regions."}}, {"model": "indicatorsets.filterdescription", "pk": 11, "fields": {"name": "geographic_levels", "description": "List only indicators that are available at any of the selected geographic levels."}}, {"model": "indicatorsets.filterdescription", "pk": 12, "fields": {"name": "severity_pyramid_rungs", "description": "List only indicators that are directly related to any of the selected rungs."}}, {"model": "indicatorsets.filterdescription", "pk": 13, "fields": {"name": "original_data_provider", "description": "List only indicator that are based on data from one of the selected sources."}}, {"model": "indicatorsets.filterdescription", "pk": 14, "fields": {"name": "temporal_granularity", "description": "The temporal resolution of this indicator (not of the reporting). Might not be the same as Reporting Cadence (e.g. a daily indicator may be reported only once a week)."}}, {"model": "indicatorsets.filterdescription", "pk": 15, "fields": {"name": "temporal_scope_end", "description": "The latest date for which this indicator is available."}}, {"model": "indicatorsets.filterdescription", "pk": 16, "fields": {"name": "location_search", "description": "Enter one or more locations for which you are looking for indicator coverage, or leave empty for all locations. Start entering a location name to see all compatible locations. Auto-complete with [Tab] or [Enter]. Currently works only for U.S. locations."}}]

src/indicatorsets/admin.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
from django.contrib import admin
22

33
from import_export.admin import ImportExportModelAdmin
4-
from indicatorsets.models import IndicatorSet, NonDelphiIndicatorSet
4+
from indicatorsets.models import (
5+
IndicatorSet,
6+
NonDelphiIndicatorSet,
7+
FilterDescription,
8+
ColumnDescription,
9+
)
510
from indicatorsets.resources import IndicatorSetResource, NonDelphiIndicatorSetResource
611

712

@@ -55,3 +60,29 @@ class NonDelphiIndicatorSetAdmin(ImportExportModelAdmin):
5560
search_fields = ("name", "short_name", "description")
5661
ordering = ["name"]
5762
list_filter = ["original_data_provider", "source_type"]
63+
64+
65+
@admin.register(FilterDescription)
66+
class FilterDescriptionAdmin(admin.ModelAdmin):
67+
"""
68+
Admin interface for the FilterDescription model.
69+
"""
70+
71+
list_display = ("name", "description")
72+
search_fields = ("name", "description")
73+
ordering = ["name"]
74+
list_filter = ["name"]
75+
list_editable = ("description",)
76+
77+
78+
@admin.register(ColumnDescription)
79+
class ColumnDescriptionAdmin(admin.ModelAdmin):
80+
"""
81+
Admin interface for the ColumnDescription model.
82+
"""
83+
84+
list_display = ("name", "description")
85+
search_fields = ("name", "description")
86+
ordering = ["name"]
87+
list_filter = ["name"]
88+
list_editable = ("description",)

0 commit comments

Comments
 (0)