squirrels 0.1.1.post1__py3-none-any.whl → 0.2.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of squirrels might be problematic. Click here for more details.
- squirrels/__init__.py +10 -16
- squirrels/_api_server.py +234 -80
- squirrels/_authenticator.py +84 -0
- squirrels/_command_line.py +60 -72
- squirrels/_connection_set.py +96 -0
- squirrels/_constants.py +114 -33
- squirrels/_environcfg.py +77 -0
- squirrels/_initializer.py +126 -67
- squirrels/_manifest.py +195 -168
- squirrels/_models.py +495 -0
- squirrels/_package_loader.py +26 -0
- squirrels/_parameter_configs.py +401 -0
- squirrels/_parameter_sets.py +188 -0
- squirrels/_py_module.py +60 -0
- squirrels/_timer.py +36 -0
- squirrels/_utils.py +81 -49
- squirrels/_version.py +2 -2
- squirrels/arguments/init_time_args.py +32 -0
- squirrels/arguments/run_time_args.py +82 -0
- squirrels/data_sources.py +380 -155
- squirrels/dateutils.py +86 -57
- squirrels/package_data/base_project/Dockerfile +15 -0
- squirrels/package_data/base_project/connections.yml +7 -0
- squirrels/package_data/base_project/database/{sample_database.db → expenses.db} +0 -0
- squirrels/package_data/base_project/environcfg.yml +29 -0
- squirrels/package_data/base_project/ignores/.dockerignore +8 -0
- squirrels/package_data/base_project/ignores/.gitignore +7 -0
- squirrels/package_data/base_project/models/dbviews/database_view1.py +36 -0
- squirrels/package_data/base_project/models/dbviews/database_view1.sql +15 -0
- squirrels/package_data/base_project/models/federates/dataset_example.py +20 -0
- squirrels/package_data/base_project/models/federates/dataset_example.sql +3 -0
- squirrels/package_data/base_project/parameters.yml +109 -0
- squirrels/package_data/base_project/pyconfigs/auth.py +47 -0
- squirrels/package_data/base_project/pyconfigs/connections.py +28 -0
- squirrels/package_data/base_project/pyconfigs/context.py +45 -0
- squirrels/package_data/base_project/pyconfigs/parameters.py +55 -0
- squirrels/package_data/base_project/seeds/mocks/category.csv +3 -0
- squirrels/package_data/base_project/seeds/mocks/max_filter.csv +2 -0
- squirrels/package_data/base_project/seeds/mocks/subcategory.csv +6 -0
- squirrels/package_data/base_project/squirrels.yml.j2 +57 -0
- squirrels/package_data/base_project/tmp/.gitignore +2 -0
- squirrels/package_data/static/script.js +159 -63
- squirrels/package_data/static/style.css +79 -15
- squirrels/package_data/static/widgets.js +133 -0
- squirrels/package_data/templates/index.html +65 -23
- squirrels/package_data/templates/index2.html +22 -0
- squirrels/parameter_options.py +216 -119
- squirrels/parameters.py +407 -478
- squirrels/user_base.py +58 -0
- squirrels-0.2.0.dev0.dist-info/METADATA +126 -0
- squirrels-0.2.0.dev0.dist-info/RECORD +56 -0
- {squirrels-0.1.1.post1.dist-info → squirrels-0.2.0.dev0.dist-info}/WHEEL +1 -2
- squirrels-0.2.0.dev0.dist-info/entry_points.txt +3 -0
- squirrels/_credentials_manager.py +0 -87
- squirrels/_module_loader.py +0 -37
- squirrels/_parameter_set.py +0 -151
- squirrels/_renderer.py +0 -286
- squirrels/_timed_imports.py +0 -37
- squirrels/connection_set.py +0 -126
- squirrels/package_data/base_project/.gitignore +0 -4
- squirrels/package_data/base_project/connections.py +0 -20
- squirrels/package_data/base_project/datasets/sample_dataset/context.py +0 -22
- squirrels/package_data/base_project/datasets/sample_dataset/database_view1.py +0 -29
- squirrels/package_data/base_project/datasets/sample_dataset/database_view1.sql.j2 +0 -12
- squirrels/package_data/base_project/datasets/sample_dataset/final_view.py +0 -11
- squirrels/package_data/base_project/datasets/sample_dataset/final_view.sql.j2 +0 -3
- squirrels/package_data/base_project/datasets/sample_dataset/parameters.py +0 -47
- squirrels/package_data/base_project/datasets/sample_dataset/selections.cfg +0 -9
- squirrels/package_data/base_project/squirrels.yaml +0 -22
- squirrels-0.1.1.post1.dist-info/METADATA +0 -67
- squirrels-0.1.1.post1.dist-info/RECORD +0 -40
- squirrels-0.1.1.post1.dist-info/entry_points.txt +0 -2
- squirrels-0.1.1.post1.dist-info/top_level.txt +0 -1
- {squirrels-0.1.1.post1.dist-info → squirrels-0.2.0.dev0.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
import squirrels as sr
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main(ctx: dict[str, Any], sqrl: sr.ContextArgs) -> None:
|
|
6
|
+
"""
|
|
7
|
+
Define context variables AFTER parameter selections are made by adding entries to the dictionary "ctx".
|
|
8
|
+
These context variables can then be used in the models.
|
|
9
|
+
|
|
10
|
+
Note that the code here is used by all datasets, regardless of the parameters they use. You can use
|
|
11
|
+
sqrl.prms and sqrl.args to determine the conditions to execute certain blocks of code.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
if "group_by" in sqrl.prms:
|
|
15
|
+
group_by_param: sr.SingleSelectParameter = sqrl.prms["group_by"]
|
|
16
|
+
ctx["group_by_cols_list"]: list[str] = group_by_param.get_selected("columns")
|
|
17
|
+
ctx["group_by_cols"] = ",".join(ctx["group_by_cols_list"])
|
|
18
|
+
ctx["order_by_cols"] = ",".join((x+" DESC") for x in ctx["group_by_cols_list"])
|
|
19
|
+
|
|
20
|
+
if "start_date" in sqrl.prms:
|
|
21
|
+
start_date_param: sr.DateParameter = sqrl.prms["start_date"]
|
|
22
|
+
ctx["start_date"] = start_date_param.get_selected_date_quoted()
|
|
23
|
+
|
|
24
|
+
if "end_date" in sqrl.prms:
|
|
25
|
+
end_date_param: sr.DateParameter = sqrl.prms["end_date"]
|
|
26
|
+
ctx["end_date"] = end_date_param.get_selected_date_quoted()
|
|
27
|
+
|
|
28
|
+
if "category" in sqrl.prms:
|
|
29
|
+
category_param: sr.MultiSelectParameter = sqrl.prms["category"]
|
|
30
|
+
ctx["has_categories"] = category_param.has_non_empty_selection()
|
|
31
|
+
ctx["categories"] = category_param.get_selected_labels_quoted_joined()
|
|
32
|
+
|
|
33
|
+
if "subcategory" in sqrl.prms:
|
|
34
|
+
subcategory_param: sr.MultiSelectParameter = sqrl.prms["subcategory"]
|
|
35
|
+
ctx["has_subcategories"] = subcategory_param.has_non_empty_selection()
|
|
36
|
+
ctx["subcategories"] = subcategory_param.get_selected_labels_quoted_joined()
|
|
37
|
+
|
|
38
|
+
if "min_filter" in sqrl.prms:
|
|
39
|
+
min_amount_filter: sr.NumberParameter = sqrl.prms["min_filter"]
|
|
40
|
+
ctx["min_amount"] = min_amount_filter.get_selected_value()
|
|
41
|
+
|
|
42
|
+
if "max_filter" in sqrl.prms:
|
|
43
|
+
max_amount_filter: sr.NumberParameter = sqrl.prms["max_filter"]
|
|
44
|
+
ctx["max_amount"] = max_amount_filter.get_selected_value()
|
|
45
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import squirrels as sr
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def main(sqrl: sr.ParametersArgs) -> None:
|
|
5
|
+
"""
|
|
6
|
+
Create all widget parameters in this file. If two or more datasets use a different set of parameters, define them all
|
|
7
|
+
here, and specify the subset of parameters used for each dataset in the "squirrels.yml" file.
|
|
8
|
+
|
|
9
|
+
Parameters are created by a factory method associated to some parameters class. For example:
|
|
10
|
+
> sr.SingleSelectParameter.Create(...)
|
|
11
|
+
|
|
12
|
+
The parameter classes available are:
|
|
13
|
+
- SingleSelectParameter, MultiSelectParameter, DateParameter, DateRangeParameter, NumberParameter, NumberRangeParameter
|
|
14
|
+
|
|
15
|
+
The factory methods available are:
|
|
16
|
+
- Create, CreateSimple, CreateFromSource
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
""" Example of creating SingleSelectParameter and specifying each option by code """
|
|
20
|
+
group_by_options = [
|
|
21
|
+
sr.SelectParameterOption("g0", "Transaction", columns=["ID", "Date"]),
|
|
22
|
+
sr.SelectParameterOption("g1", "Date", columns=["Date"]),
|
|
23
|
+
sr.SelectParameterOption("g2", "Category", columns=["Category"]),
|
|
24
|
+
sr.SelectParameterOption("g3", "Subcategory", columns=["Category", "Subcategory"]),
|
|
25
|
+
]
|
|
26
|
+
sr.SingleSelectParameter.Create("group_by", "Group By", group_by_options)
|
|
27
|
+
|
|
28
|
+
""" Example of creating DateParameter """
|
|
29
|
+
sr.DateParameter.CreateSimple("start_date", "Start Date", "2023-01-01")
|
|
30
|
+
|
|
31
|
+
""" Example of creating DateParameter from list of DateParameterOption's """
|
|
32
|
+
end_date_option = [sr.DateParameterOption("2023-12-31")]
|
|
33
|
+
sr.DateParameter.Create("end_date", "End Date", end_date_option)
|
|
34
|
+
|
|
35
|
+
""" Example of creating DateRangeParameter """
|
|
36
|
+
sr.DateRangeParameter.CreateSimple("date_range", "Date Range", "2023-01-01", "2023-12-31")
|
|
37
|
+
|
|
38
|
+
""" Example of creating MultiSelectParameter from lookup query/table """
|
|
39
|
+
category_ds = sr.MultiSelectDataSource("categories", "Category_ID", "Category")
|
|
40
|
+
sr.MultiSelectParameter.CreateFromSource("category", "Category Filter", category_ds)
|
|
41
|
+
|
|
42
|
+
""" Example of creating MultiSelectParameter with parent from lookup query/table """
|
|
43
|
+
subcategory_ds = sr.MultiSelectDataSource("subcategories", "Subcategory_ID", "Subcategory", parent_id_col="Category_ID")
|
|
44
|
+
sr.MultiSelectParameter.CreateFromSource("subcategory", "Subcategory Filter", subcategory_ds, parent_name="category")
|
|
45
|
+
|
|
46
|
+
""" Example of creating NumberParameter """
|
|
47
|
+
sr.NumberParameter.CreateSimple("min_filter", "Amounts Greater Than", min_value=0, max_value=500, increment=10)
|
|
48
|
+
|
|
49
|
+
""" Example of creating NumberParameter from lookup query/table """
|
|
50
|
+
query = "SELECT 0 as min_value, max(-Amount) as max_value, 10 as increment FROM transactions WHERE Category <> 'Income'"
|
|
51
|
+
max_amount_ds = sr.NumberDataSource(query, "min_value", "max_value", increment_col="increment", default_value_col="max_value")
|
|
52
|
+
sr.NumberParameter.CreateFromSource("max_filter", "Amounts Less Than", max_amount_ds)
|
|
53
|
+
|
|
54
|
+
""" Example of creating NumberRangeParameter """
|
|
55
|
+
sr.NumberRangeParameter.CreateSimple("between_filter", "Amounts Between", 0, 500, default_lower_value=10, default_upper_value=400)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
project_variables:
|
|
2
|
+
name: sample
|
|
3
|
+
label: Sample Product
|
|
4
|
+
major_version: 1
|
|
5
|
+
minor_version: 0
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
packages: []
|
|
9
|
+
|
|
10
|
+
## Example value for packages:
|
|
11
|
+
# - git: https://.../myrepo.git
|
|
12
|
+
# revision: v0.1.0
|
|
13
|
+
# directory: custom_name ## optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
{{ connections -}}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
{{ parameters -}}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
selection_test_sets:
|
|
23
|
+
- name: sample_test_set
|
|
24
|
+
user_attributes: ## optional unless there are user attributes used by parameters or models
|
|
25
|
+
organization: org1
|
|
26
|
+
parameters: ## optional - for unspecified parameters, default value is used
|
|
27
|
+
group_by: g3
|
|
28
|
+
start_date: 2023-02-01
|
|
29
|
+
end_date: 2023-12-01
|
|
30
|
+
category: ["1"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
datasets:
|
|
34
|
+
- name: dataset_example ## model with same name is used unless "model" field is specified for dataset
|
|
35
|
+
label: Dataset Example
|
|
36
|
+
scope: public ## optional - one of 'public' (default), 'protected', or 'private'
|
|
37
|
+
parameters: ## optional - if not specified, then all parameters are used
|
|
38
|
+
- group_by
|
|
39
|
+
- start_date
|
|
40
|
+
- end_date
|
|
41
|
+
- category
|
|
42
|
+
- subcategory
|
|
43
|
+
- min_filter
|
|
44
|
+
- max_filter
|
|
45
|
+
args: {} ## optional
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
settings: {}
|
|
49
|
+
|
|
50
|
+
## Default values for settings:
|
|
51
|
+
# auth.token.expire.minutes: 30
|
|
52
|
+
# parameters.cache.size: 1024
|
|
53
|
+
# parameters.cache.ttl.minutes: 0
|
|
54
|
+
# results.cache.size: 128
|
|
55
|
+
# results.cache.ttl.minutes: 0
|
|
56
|
+
# defaults.dbviews.connection_name: default
|
|
57
|
+
# defaults.federates.materialized: table
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const projectSelect = document.getElementById('project-select');
|
|
1
2
|
const datasetSelect = document.getElementById('dataset-select');
|
|
2
3
|
const generatedParamsDiv = document.getElementById('generated-parameters');
|
|
3
4
|
|
|
@@ -8,33 +9,93 @@ const tableBody = document.getElementById('table-body');
|
|
|
8
9
|
|
|
9
10
|
const loadingIndicator = document.getElementById('loading-indicator');
|
|
10
11
|
|
|
12
|
+
const loginModal = document.getElementById('login-modal');
|
|
13
|
+
const loginForm = document.getElementById('login-form');
|
|
14
|
+
const incorrectPwdMessage = document.getElementById('incorrectpwd');
|
|
15
|
+
let loginProcessor = null;
|
|
16
|
+
|
|
17
|
+
const usernameTxt = document.getElementById('username-txt');
|
|
18
|
+
|
|
11
19
|
const datasetsMap = new Map();
|
|
12
20
|
const parametersMap = new Map();
|
|
13
21
|
|
|
14
|
-
function
|
|
22
|
+
async function loginEvent(event) {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
const formData = new FormData(loginForm);
|
|
25
|
+
const response = await fetch(token_path, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
body: formData
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (response.status === 401) {
|
|
31
|
+
incorrectPwdMessage.style.display = 'block'
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
sessionStorage.setItem("jwt_token", data.access_token);
|
|
36
|
+
sessionStorage.setItem("username", `"${data.username}"`);
|
|
37
|
+
callJsonAPI(catalog_path, renderDatasetsSelection);
|
|
38
|
+
updateUsername();
|
|
39
|
+
closeLoginModal();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function updateUsername() {
|
|
44
|
+
const username = sessionStorage.getItem("username");
|
|
45
|
+
usernameTxt.innerHTML = (username === null) ? "guest" : username;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function openLoginModal() {
|
|
49
|
+
loginModal.style.display = 'flex';
|
|
50
|
+
loadingIndicator.style.display = 'none';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function closeLoginModal() {
|
|
54
|
+
loginForm.reset()
|
|
55
|
+
loginModal.style.display = 'none';
|
|
56
|
+
incorrectPwdMessage.style.display = 'none';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function callJsonAPI(path, func) {
|
|
15
60
|
loadingIndicator.style.display = 'flex';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
61
|
+
const jwt_token = sessionStorage.getItem("jwt_token");
|
|
62
|
+
const response = await fetch(path, {
|
|
63
|
+
headers: {
|
|
64
|
+
'Authorization': `Bearer ${jwt_token}`
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (response.status === 401) {
|
|
69
|
+
alert("Unauthorized action. Please try to 'Authorize' first");
|
|
70
|
+
}
|
|
71
|
+
else if (response.status === 200) {
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
func(data);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const error = `Unexpected response status: ${response.status}`;
|
|
77
|
+
alert(error);
|
|
78
|
+
}
|
|
79
|
+
loadingIndicator.style.display = 'none';
|
|
28
80
|
}
|
|
29
81
|
|
|
30
|
-
function changeDatasetSelection() {
|
|
82
|
+
async function changeDatasetSelection() {
|
|
31
83
|
tableContainers.style.display = 'none';
|
|
32
84
|
parametersMap.clear()
|
|
33
85
|
refreshParameters();
|
|
34
86
|
}
|
|
35
87
|
|
|
36
|
-
function renderDatasetsSelection(data) {
|
|
37
|
-
|
|
88
|
+
async function renderDatasetsSelection(data) {
|
|
89
|
+
projectSelect.innerHTML = "";
|
|
90
|
+
datasetSelect.innerHTML = "";
|
|
91
|
+
|
|
92
|
+
const product = data.products[0];
|
|
93
|
+
const option = document.createElement('option');
|
|
94
|
+
option.value = product.name;
|
|
95
|
+
option.textContent = product.label;
|
|
96
|
+
projectSelect.appendChild(option);
|
|
97
|
+
|
|
98
|
+
const datasets = product.versions[0].datasets;
|
|
38
99
|
datasets.forEach(resource => {
|
|
39
100
|
const option = document.createElement('option');
|
|
40
101
|
option.value = resource.name;
|
|
@@ -42,18 +103,23 @@ function renderDatasetsSelection(data) {
|
|
|
42
103
|
datasetSelect.appendChild(option);
|
|
43
104
|
datasetsMap.set(option.value, resource);
|
|
44
105
|
});
|
|
45
|
-
|
|
106
|
+
|
|
107
|
+
if (datasets.length === 0) {
|
|
108
|
+
alert("No datasets available! Authentication may be required to access.");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
changeDatasetSelection();
|
|
112
|
+
}
|
|
46
113
|
}
|
|
47
114
|
|
|
48
|
-
function refreshParameters(provoker = null) {
|
|
115
|
+
async function refreshParameters(provoker = null) {
|
|
49
116
|
const selectedDatasetValue = datasetSelect.value;
|
|
50
117
|
const parametersPath = datasetsMap.get(selectedDatasetValue).parameters_path;
|
|
51
|
-
const queryParameters = getQueryParams(provoker)
|
|
118
|
+
const queryParameters = await getQueryParams(provoker)
|
|
52
119
|
const parametersRequest = parametersPath + '?' + queryParameters
|
|
53
|
-
console.log('Parameters request:', parametersRequest)
|
|
54
120
|
|
|
55
121
|
callJsonAPI(parametersRequest, (jsonResponse) => {
|
|
56
|
-
jsonResponse.parameters.forEach(
|
|
122
|
+
jsonResponse.parameters.forEach(param => {
|
|
57
123
|
parametersMap.set(param.name, param);
|
|
58
124
|
})
|
|
59
125
|
generatedParamsDiv.innerHTML = "";
|
|
@@ -66,7 +132,7 @@ function refreshParameters(provoker = null) {
|
|
|
66
132
|
newDiv.appendChild(paramLabel)
|
|
67
133
|
}
|
|
68
134
|
|
|
69
|
-
if (param.widget_type === "
|
|
135
|
+
if (param.widget_type === "date") {
|
|
70
136
|
addLabel()
|
|
71
137
|
const dateInput = document.createElement('input')
|
|
72
138
|
dateInput.type = 'date'
|
|
@@ -74,7 +140,12 @@ function refreshParameters(provoker = null) {
|
|
|
74
140
|
dateInput.value = param.selected_date
|
|
75
141
|
dateInput.onchange = updateParameter
|
|
76
142
|
newDiv.appendChild(dateInput)
|
|
77
|
-
}
|
|
143
|
+
}
|
|
144
|
+
else if (param.widget_type === "date_range") {
|
|
145
|
+
// TODO
|
|
146
|
+
addLabel()
|
|
147
|
+
}
|
|
148
|
+
else if (param.widget_type === "number") {
|
|
78
149
|
addLabel()
|
|
79
150
|
const sliderInput = document.createElement('input')
|
|
80
151
|
sliderInput.type = 'range'
|
|
@@ -96,13 +167,16 @@ function refreshParameters(provoker = null) {
|
|
|
96
167
|
|
|
97
168
|
newDiv.appendChild(sliderInput)
|
|
98
169
|
newDiv.appendChild(sliderValue)
|
|
99
|
-
}
|
|
170
|
+
}
|
|
171
|
+
else if (param.widget_type === "number_range") {
|
|
100
172
|
// TODO
|
|
101
|
-
|
|
173
|
+
addLabel()
|
|
174
|
+
}
|
|
175
|
+
else if (param.widget_type === "single_select" && param.options.length > 0) {
|
|
102
176
|
addLabel()
|
|
103
177
|
const singleSelect = document.createElement('select');
|
|
104
178
|
singleSelect.id = param.name;
|
|
105
|
-
param.options.forEach(
|
|
179
|
+
param.options.forEach(option => {
|
|
106
180
|
const selectOption = document.createElement('option');
|
|
107
181
|
selectOption.value = option.id;
|
|
108
182
|
if (option.id === param.selected_id) {
|
|
@@ -113,12 +187,13 @@ function refreshParameters(provoker = null) {
|
|
|
113
187
|
});
|
|
114
188
|
singleSelect.onchange = updateParameter
|
|
115
189
|
newDiv.appendChild(singleSelect);
|
|
116
|
-
}
|
|
190
|
+
}
|
|
191
|
+
else if (param.widget_type === "multi_select" && param.options.length > 0) {
|
|
117
192
|
addLabel()
|
|
118
193
|
const multiSelect = document.createElement('select');
|
|
119
194
|
multiSelect.id = param.name;
|
|
120
195
|
multiSelect.multiple = true;
|
|
121
|
-
param.options.forEach(
|
|
196
|
+
param.options.forEach(option => {
|
|
122
197
|
const selectOption = document.createElement('option');
|
|
123
198
|
selectOption.value = option.id;
|
|
124
199
|
if (param.selected_ids.includes(option.id)) {
|
|
@@ -135,17 +210,24 @@ function refreshParameters(provoker = null) {
|
|
|
135
210
|
});
|
|
136
211
|
}
|
|
137
212
|
|
|
138
|
-
function updateParameter() {
|
|
213
|
+
async function updateParameter() {
|
|
139
214
|
const param = parametersMap.get(this.id)
|
|
140
|
-
if (param.widget_type === "
|
|
215
|
+
if (param.widget_type === "date") {
|
|
141
216
|
param.selected_date = this.value
|
|
142
|
-
}
|
|
217
|
+
}
|
|
218
|
+
else if (param.widget_type === "date_range") {
|
|
219
|
+
// TODO
|
|
220
|
+
}
|
|
221
|
+
else if (param.widget_type === "number") {
|
|
143
222
|
param.selected_value = this.value
|
|
144
|
-
}
|
|
223
|
+
}
|
|
224
|
+
else if (param.widget_type === "number_range") {
|
|
145
225
|
// TODO
|
|
146
|
-
}
|
|
226
|
+
}
|
|
227
|
+
else if (param.widget_type === "single_select") {
|
|
147
228
|
param.selected_id = this.options[this.selectedIndex].value
|
|
148
|
-
}
|
|
229
|
+
}
|
|
230
|
+
else if (param.widget_type === "multi_select") {
|
|
149
231
|
param.selected_ids = [...this.selectedOptions].map(option => option.value)
|
|
150
232
|
}
|
|
151
233
|
|
|
@@ -154,19 +236,26 @@ function updateParameter() {
|
|
|
154
236
|
}
|
|
155
237
|
}
|
|
156
238
|
|
|
157
|
-
function getQueryParams(provoker = null) {
|
|
239
|
+
async function getQueryParams(provoker = null) {
|
|
158
240
|
const queryParams = {}
|
|
159
|
-
function addToQueryParams(key,
|
|
160
|
-
if (
|
|
161
|
-
queryParams[key] =
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
queryParams[key] =
|
|
168
|
-
}
|
|
169
|
-
|
|
241
|
+
function addToQueryParams(key, param) {
|
|
242
|
+
if (param.widget_type === "date") {
|
|
243
|
+
queryParams[key] = param.selected_date
|
|
244
|
+
}
|
|
245
|
+
else if (param.widget_type === "date_range") {
|
|
246
|
+
queryParams[key] = param.selected_start_date + ',' + param.selected_end_date
|
|
247
|
+
}
|
|
248
|
+
else if (param.widget_type === "number") {
|
|
249
|
+
queryParams[key] = param.selected_value
|
|
250
|
+
}
|
|
251
|
+
else if (param.widget_type === "number_range") {
|
|
252
|
+
queryParams[key] = param.selected_lower_value + ',' + param.selected_upper_value
|
|
253
|
+
}
|
|
254
|
+
else if (param.widget_type === "single_select") {
|
|
255
|
+
queryParams[key] = param.selected_id
|
|
256
|
+
}
|
|
257
|
+
else if (param.widget_type === "multi_select") {
|
|
258
|
+
const result = JSON.stringify(param.selected_ids)
|
|
170
259
|
if (result !== '') queryParams[key] = result
|
|
171
260
|
}
|
|
172
261
|
}
|
|
@@ -174,19 +263,18 @@ function getQueryParams(provoker = null) {
|
|
|
174
263
|
addToQueryParams(provoker.name, provoker)
|
|
175
264
|
}
|
|
176
265
|
else {
|
|
177
|
-
for (const [key,
|
|
178
|
-
addToQueryParams(key,
|
|
266
|
+
for (const [key, param] of parametersMap.entries()) {
|
|
267
|
+
addToQueryParams(key, param)
|
|
179
268
|
}
|
|
180
269
|
}
|
|
181
|
-
console.log(queryParams)
|
|
182
270
|
return new URLSearchParams(queryParams)
|
|
183
271
|
}
|
|
184
272
|
|
|
185
|
-
function getDatasetResults() {
|
|
273
|
+
async function getDatasetResults() {
|
|
186
274
|
const selectedDatasetValue = datasetSelect.value;
|
|
187
275
|
const resultPath = datasetsMap.get(selectedDatasetValue).result_path;
|
|
188
|
-
const
|
|
189
|
-
|
|
276
|
+
const queryParams = await getQueryParams();
|
|
277
|
+
const resultRequest = resultPath + '?' + queryParams
|
|
190
278
|
|
|
191
279
|
callJsonAPI(resultRequest, (jsonResponse) => {
|
|
192
280
|
tableHeader.innerHTML = ''
|
|
@@ -216,19 +304,27 @@ function getDatasetResults() {
|
|
|
216
304
|
});
|
|
217
305
|
}
|
|
218
306
|
|
|
219
|
-
function copyTable() {
|
|
307
|
+
async function copyTable() {
|
|
220
308
|
let text = "";
|
|
221
309
|
|
|
222
310
|
for (let i = 0; i < resultTable.rows.length; i++) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
311
|
+
for (let j = 0; j < resultTable.rows[i].cells.length; j++) {
|
|
312
|
+
text += resultTable.rows[i].cells[j].innerHTML + "\t";
|
|
313
|
+
}
|
|
314
|
+
text += "\n";
|
|
227
315
|
}
|
|
228
316
|
|
|
229
|
-
navigator.clipboard.writeText(text).then(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
317
|
+
navigator.clipboard.writeText(text).then(
|
|
318
|
+
() => {
|
|
319
|
+
alert("Table copied to clipboard!");
|
|
320
|
+
},
|
|
321
|
+
() => {
|
|
322
|
+
alert("Copying failed.");
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
loginForm.addEventListener('submit', loginEvent);
|
|
329
|
+
loginForm.addEventListener('reset', closeLoginModal);
|
|
330
|
+
updateUsername();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* Generic styling */
|
|
1
2
|
table {
|
|
2
3
|
border-collapse: collapse;
|
|
3
4
|
width: 100%;
|
|
@@ -34,44 +35,85 @@ input[type="range"] {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
input[type=submit] {
|
|
38
|
+
padding: 12px 20px;
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
button {
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
border-radius: 5px;
|
|
45
|
+
padding: 10px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.white-button {
|
|
49
|
+
background-color: white;
|
|
50
|
+
color: blue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.white-button:hover {
|
|
54
|
+
background-color: #d4dae1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.blue-button {
|
|
37
58
|
background-color: #4285f4;
|
|
38
59
|
color: white;
|
|
39
|
-
padding: 12px 20px;
|
|
40
60
|
border: none;
|
|
41
|
-
border-radius: 4px;
|
|
42
|
-
cursor: pointer;
|
|
43
61
|
}
|
|
44
62
|
|
|
45
|
-
|
|
63
|
+
.blue-button:hover {
|
|
46
64
|
background-color: #3076c5;
|
|
47
65
|
}
|
|
48
66
|
|
|
49
|
-
|
|
67
|
+
.horizontal-container {
|
|
50
68
|
display: flex;
|
|
51
69
|
}
|
|
52
70
|
|
|
71
|
+
.slider-value {
|
|
72
|
+
display: inline-block;
|
|
73
|
+
position: relative;
|
|
74
|
+
left: 10px;
|
|
75
|
+
bottom: 14px;
|
|
76
|
+
font-size: 18px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Specific styling */
|
|
80
|
+
#main-container {
|
|
81
|
+
height: 97vh;
|
|
82
|
+
width: 99vw;
|
|
83
|
+
}
|
|
84
|
+
|
|
53
85
|
#parameter-container {
|
|
54
86
|
width: 250px;
|
|
55
87
|
min-width: 250px;
|
|
56
|
-
margin-right:
|
|
88
|
+
margin-right: 20px;
|
|
89
|
+
padding: 10px;
|
|
90
|
+
overflow-y: auto;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#right-container {
|
|
94
|
+
height: 97vh;
|
|
95
|
+
width: calc(99vw - 300px);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#header-container {
|
|
99
|
+
background-color: #d4dae1;
|
|
100
|
+
color: black;
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: space-between;
|
|
57
104
|
padding: 10px;
|
|
58
105
|
}
|
|
59
106
|
|
|
60
107
|
#table-container {
|
|
61
108
|
display: none;
|
|
62
109
|
padding: 10px 20px;
|
|
110
|
+
height: calc(97vh - 70px);
|
|
111
|
+
overflow-x: auto;
|
|
112
|
+
overflow-y: auto;
|
|
63
113
|
}
|
|
64
114
|
|
|
65
115
|
#result-table {
|
|
66
|
-
margin:
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.slider-value {
|
|
70
|
-
display: inline-block;
|
|
71
|
-
position: relative;
|
|
72
|
-
left: 10px;
|
|
73
|
-
bottom: 14px;
|
|
74
|
-
font-size: 18px;
|
|
116
|
+
margin: 5px 0px;
|
|
75
117
|
}
|
|
76
118
|
|
|
77
119
|
/* Loading section */
|
|
@@ -108,3 +150,25 @@ input[type=submit]:hover {
|
|
|
108
150
|
transform: rotate(360deg);
|
|
109
151
|
}
|
|
110
152
|
}
|
|
153
|
+
|
|
154
|
+
/* The Modal */
|
|
155
|
+
.modal-background {
|
|
156
|
+
display: none;
|
|
157
|
+
position: fixed;
|
|
158
|
+
z-index: 1;
|
|
159
|
+
left: 0;
|
|
160
|
+
top: 0;
|
|
161
|
+
width: 100%;
|
|
162
|
+
height: 100%;
|
|
163
|
+
overflow: auto;
|
|
164
|
+
background-color: rgba(0,0,0,0.4);
|
|
165
|
+
padding-top: 60px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.modal-content {
|
|
169
|
+
background-color: #fefefe;
|
|
170
|
+
padding: 16px;
|
|
171
|
+
margin: 5px auto auto auto;
|
|
172
|
+
border: 1px solid #888;
|
|
173
|
+
width: 40%;
|
|
174
|
+
}
|