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.

Files changed (74) hide show
  1. squirrels/__init__.py +10 -16
  2. squirrels/_api_server.py +234 -80
  3. squirrels/_authenticator.py +84 -0
  4. squirrels/_command_line.py +60 -72
  5. squirrels/_connection_set.py +96 -0
  6. squirrels/_constants.py +114 -33
  7. squirrels/_environcfg.py +77 -0
  8. squirrels/_initializer.py +126 -67
  9. squirrels/_manifest.py +195 -168
  10. squirrels/_models.py +495 -0
  11. squirrels/_package_loader.py +26 -0
  12. squirrels/_parameter_configs.py +401 -0
  13. squirrels/_parameter_sets.py +188 -0
  14. squirrels/_py_module.py +60 -0
  15. squirrels/_timer.py +36 -0
  16. squirrels/_utils.py +81 -49
  17. squirrels/_version.py +2 -2
  18. squirrels/arguments/init_time_args.py +32 -0
  19. squirrels/arguments/run_time_args.py +82 -0
  20. squirrels/data_sources.py +380 -155
  21. squirrels/dateutils.py +86 -57
  22. squirrels/package_data/base_project/Dockerfile +15 -0
  23. squirrels/package_data/base_project/connections.yml +7 -0
  24. squirrels/package_data/base_project/database/{sample_database.db → expenses.db} +0 -0
  25. squirrels/package_data/base_project/environcfg.yml +29 -0
  26. squirrels/package_data/base_project/ignores/.dockerignore +8 -0
  27. squirrels/package_data/base_project/ignores/.gitignore +7 -0
  28. squirrels/package_data/base_project/models/dbviews/database_view1.py +36 -0
  29. squirrels/package_data/base_project/models/dbviews/database_view1.sql +15 -0
  30. squirrels/package_data/base_project/models/federates/dataset_example.py +20 -0
  31. squirrels/package_data/base_project/models/federates/dataset_example.sql +3 -0
  32. squirrels/package_data/base_project/parameters.yml +109 -0
  33. squirrels/package_data/base_project/pyconfigs/auth.py +47 -0
  34. squirrels/package_data/base_project/pyconfigs/connections.py +28 -0
  35. squirrels/package_data/base_project/pyconfigs/context.py +45 -0
  36. squirrels/package_data/base_project/pyconfigs/parameters.py +55 -0
  37. squirrels/package_data/base_project/seeds/mocks/category.csv +3 -0
  38. squirrels/package_data/base_project/seeds/mocks/max_filter.csv +2 -0
  39. squirrels/package_data/base_project/seeds/mocks/subcategory.csv +6 -0
  40. squirrels/package_data/base_project/squirrels.yml.j2 +57 -0
  41. squirrels/package_data/base_project/tmp/.gitignore +2 -0
  42. squirrels/package_data/static/script.js +159 -63
  43. squirrels/package_data/static/style.css +79 -15
  44. squirrels/package_data/static/widgets.js +133 -0
  45. squirrels/package_data/templates/index.html +65 -23
  46. squirrels/package_data/templates/index2.html +22 -0
  47. squirrels/parameter_options.py +216 -119
  48. squirrels/parameters.py +407 -478
  49. squirrels/user_base.py +58 -0
  50. squirrels-0.2.0.dev0.dist-info/METADATA +126 -0
  51. squirrels-0.2.0.dev0.dist-info/RECORD +56 -0
  52. {squirrels-0.1.1.post1.dist-info → squirrels-0.2.0.dev0.dist-info}/WHEEL +1 -2
  53. squirrels-0.2.0.dev0.dist-info/entry_points.txt +3 -0
  54. squirrels/_credentials_manager.py +0 -87
  55. squirrels/_module_loader.py +0 -37
  56. squirrels/_parameter_set.py +0 -151
  57. squirrels/_renderer.py +0 -286
  58. squirrels/_timed_imports.py +0 -37
  59. squirrels/connection_set.py +0 -126
  60. squirrels/package_data/base_project/.gitignore +0 -4
  61. squirrels/package_data/base_project/connections.py +0 -20
  62. squirrels/package_data/base_project/datasets/sample_dataset/context.py +0 -22
  63. squirrels/package_data/base_project/datasets/sample_dataset/database_view1.py +0 -29
  64. squirrels/package_data/base_project/datasets/sample_dataset/database_view1.sql.j2 +0 -12
  65. squirrels/package_data/base_project/datasets/sample_dataset/final_view.py +0 -11
  66. squirrels/package_data/base_project/datasets/sample_dataset/final_view.sql.j2 +0 -3
  67. squirrels/package_data/base_project/datasets/sample_dataset/parameters.py +0 -47
  68. squirrels/package_data/base_project/datasets/sample_dataset/selections.cfg +0 -9
  69. squirrels/package_data/base_project/squirrels.yaml +0 -22
  70. squirrels-0.1.1.post1.dist-info/METADATA +0 -67
  71. squirrels-0.1.1.post1.dist-info/RECORD +0 -40
  72. squirrels-0.1.1.post1.dist-info/entry_points.txt +0 -2
  73. squirrels-0.1.1.post1.dist-info/top_level.txt +0 -1
  74. {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,3 @@
1
+ Category_ID,Category
2
+ 0,Food
3
+ 1,Bills
@@ -0,0 +1,2 @@
1
+ min_value,max_value,increment
2
+ 0,500,10
@@ -0,0 +1,6 @@
1
+ Subcategory_ID,Subcategory,Category_ID
2
+ 0,Dining Out,0
3
+ 1,Groceries,0
4
+ 2,Utilities,1
5
+ 3,Mobile,1
6
+ 4,Internet,1
@@ -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
@@ -0,0 +1,2 @@
1
+ *
2
+ !.gitignore
@@ -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 callJsonAPI(path, func) {
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
- fetch(path)
17
- .then(response => response.json())
18
- .then(data => {
19
- func(data);
20
- })
21
- .catch(error => {
22
- alert('Server error...')
23
- console.log(error)
24
- })
25
- .then(_ => {
26
- loadingIndicator.style.display = 'none'
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
- const datasets = data.products[0].versions[0].datasets
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
- changeDatasetSelection();
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(function(param) {
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 === "DateParameter") {
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
- } else if (param.widget_type === "NumberParameter") {
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
- } else if (param.widget_type === "NumRangeParameter") {
170
+ }
171
+ else if (param.widget_type === "number_range") {
100
172
  // TODO
101
- } else if (param.widget_type === "SingleSelectParameter" && param.options.length > 0) {
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(function(option) {
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
- } else if (param.widget_type === "MultiSelectParameter" && param.options.length > 0) {
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(function(option) {
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 === "DateParameter") {
215
+ if (param.widget_type === "date") {
141
216
  param.selected_date = this.value
142
- } else if (param.widget_type === "NumberParameter") {
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
- } else if (param.widget_type === "NumRangeParameter") {
223
+ }
224
+ else if (param.widget_type === "number_range") {
145
225
  // TODO
146
- } else if (param.widget_type === "SingleSelectParameter") {
226
+ }
227
+ else if (param.widget_type === "single_select") {
147
228
  param.selected_id = this.options[this.selectedIndex].value
148
- } else if (param.widget_type === "MultiSelectParameter") {
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, value) {
160
- if (value.widget_type === "DateParameter") {
161
- queryParams[key] = value.selected_date
162
- } else if (value.widget_type === "NumberParameter") {
163
- queryParams[key] = value.selected_value
164
- } else if (value.widget_type === "NumRangeParameter") {
165
- // TODO
166
- } else if (value.widget_type === "SingleSelectParameter") {
167
- queryParams[key] = value.selected_id
168
- } else if (value.widget_type === "MultiSelectParameter") {
169
- result = JSON.stringify(value.selected_ids)
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, value] of parametersMap.entries()) {
178
- addToQueryParams(key, value)
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 resultRequest = resultPath + '?' + getQueryParams()
189
- console.log('Result request:', resultRequest)
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
- for (let j = 0; j < resultTable.rows[i].cells.length; j++) {
224
- text += resultTable.rows[i].cells[j].innerHTML + "\t";
225
- }
226
- text += "\n";
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(function() {
230
- alert("Table copied to clipboard!");
231
- }, function() {
232
- alert("Copying failed.");
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
- input[type=submit]:hover {
63
+ .blue-button:hover {
46
64
  background-color: #3076c5;
47
65
  }
48
66
 
49
- #main-container {
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: 50px;
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: 20px 0px;
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
+ }