ositah 24.4.dev1__py3-none-any.whl → 24.7.dev1__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.
Files changed (47) hide show
  1. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/METADATA +1 -1
  2. ositah-24.7.dev1.dist-info/RECORD +6 -0
  3. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/WHEEL +1 -1
  4. ositah/__init__.py +0 -0
  5. ositah/app.py +0 -17
  6. ositah/apps/__init__.py +0 -0
  7. ositah/apps/analysis.py +0 -774
  8. ositah/apps/configuration/__init__.py +0 -0
  9. ositah/apps/configuration/callbacks.py +0 -917
  10. ositah/apps/configuration/main.py +0 -542
  11. ositah/apps/configuration/parameters.py +0 -74
  12. ositah/apps/configuration/tools.py +0 -112
  13. ositah/apps/export.py +0 -1172
  14. ositah/apps/validation/__init__.py +0 -0
  15. ositah/apps/validation/callbacks.py +0 -240
  16. ositah/apps/validation/main.py +0 -89
  17. ositah/apps/validation/parameters.py +0 -25
  18. ositah/apps/validation/tables.py +0 -654
  19. ositah/apps/validation/tools.py +0 -533
  20. ositah/assets/arrow_down_up.svg +0 -4
  21. ositah/assets/ositah.css +0 -54
  22. ositah/assets/sort_ascending.svg +0 -5
  23. ositah/assets/sort_descending.svg +0 -6
  24. ositah/assets/sorttable.js +0 -499
  25. ositah/main.py +0 -449
  26. ositah/ositah.example.cfg +0 -215
  27. ositah/static/style.css +0 -54
  28. ositah/templates/base.html +0 -22
  29. ositah/templates/bootstrap_login.html +0 -38
  30. ositah/templates/login_form.html +0 -27
  31. ositah/utils/__init__.py +0 -0
  32. ositah/utils/agents.py +0 -117
  33. ositah/utils/authentication.py +0 -287
  34. ositah/utils/cache.py +0 -19
  35. ositah/utils/core.py +0 -13
  36. ositah/utils/exceptions.py +0 -64
  37. ositah/utils/hito_db.py +0 -51
  38. ositah/utils/hito_db_model.py +0 -245
  39. ositah/utils/menus.py +0 -339
  40. ositah/utils/period.py +0 -135
  41. ositah/utils/projects.py +0 -1175
  42. ositah/utils/teams.py +0 -42
  43. ositah/utils/utils.py +0 -459
  44. ositah-24.4.dev1.dist-info/RECORD +0 -46
  45. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/LICENSE +0 -0
  46. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/entry_points.txt +0 -0
  47. {ositah-24.4.dev1.dist-info → ositah-24.7.dev1.dist-info}/top_level.txt +0 -0
ositah/apps/analysis.py DELETED
@@ -1,774 +0,0 @@
1
- # OSITAH sub-application to analyse data to NSIP
2
- from typing import Dict
3
-
4
- import dash
5
- import dash_bootstrap_components as dbc
6
- import numpy as np
7
- import plotly.express as px
8
- from dash import dcc, html
9
- from dash.dependencies import Input, Output, State
10
- from dash.exceptions import PreventUpdate
11
-
12
- from ositah.app import app
13
- from ositah.utils.cache import clear_cached_data
14
- from ositah.utils.menus import (
15
- DATA_SELECTED_SOURCE_ID,
16
- DATA_SELECTION_SOURCE_ID,
17
- TABLE_TYPE_DUMMY_STORE,
18
- TABLE_TYPE_TABLE,
19
- TEAM_SELECTED_VALUE_ID,
20
- TEAM_SELECTION_DATE_ID,
21
- VALIDATION_PERIOD_SELECTED_ID,
22
- build_accordion,
23
- create_progress_bar,
24
- team_list_dropdown,
25
- )
26
- from ositah.utils.period import get_validation_period_dates
27
- from ositah.utils.projects import (
28
- DATA_SOURCE_HITO,
29
- DATA_SOURCE_OSITAH,
30
- build_projects_data,
31
- get_team_projects,
32
- )
33
- from ositah.utils.utils import WEEK_HOURS, GlobalParams, general_error_jumbotron
34
-
35
- ANALYSIS_TAB_MENU_ID = "report-tabs"
36
- TAB_ID_ANALYSIS_GRAPHICS = "graphics-page"
37
- TAB_ID_ANALYSIS_IJCLAB = "project-report-page"
38
-
39
- TAB_MENU_ANALYSIS_GRAPHICS = "Graphiques"
40
- TAB_MENU_ANALYSIS_IJCLAB = "Rapports"
41
-
42
- TABLE_TEAM_PROJECTS_ID = "analysis-ijclab"
43
-
44
- ANALYSIS_LOAD_INDICATOR_ID = "analysis-others-data-load-indicator"
45
- ANALYSIS_SAVED_INDICATOR_ID = "analysis-others-saved-data-load-indicator"
46
- ANALYSIS_TRIGGER_INTERVAL_ID = "analysis-others-display-callback-interval"
47
- ANALYSIS_PROGRESS_BAR_MAX_DURATION = 8 # seconds
48
- ANALYSIS_SAVED_ACTIVE_TAB_ID = "analysis-saved-active-tab"
49
-
50
- GRAPHICS_DROPDOWN_ID = "graphics-type-selection"
51
- GRAPHICS_DROPDOWN_MENU = "Types de graphique"
52
- GRAPHICS_DM_CATEGORY_TIME_ID = "graphics-cateogry-time"
53
- GRAPHICS_DM_CATEGORY_TIME_MENU = "Catégorie d'activités"
54
- GRAPHICS_DM_LOCAL_PROJECTS_TIME_ID = "graphics-local-projects-time"
55
- GRAPHICS_DM_LOCAL_PROJECTS_TIME_MENU = "Projets locaux"
56
- GRAPHICS_DM_NSIP_PROJECTS_TIME_ID = "graphics-nsip-projects-time"
57
- GRAPHICS_DM_NSIP_PROJECTS_TIME_MENU = "Projets NSIP"
58
- GRAPHICS_DM_TEACHING_ACTIVITIES_TIME_ID = "graphics-teaching-activities-time"
59
- GRAPHICS_DM_TEACHING_ACTIVITIES_TIME_MENU = "Enseignement"
60
- GRAPHICS_DM_CONSULTANCY_ACTIVITIES_TIME_ID = "graphics-consultancy-activities-time"
61
- GRAPHICS_DM_CONSULTANCY_ACTIVITIES_TIME_MENU = "Consultance & Expertise"
62
- GRAPHICS_DM_SUPPORT_ACTIVITIES_TIME_ID = "graphics-support-activities-time"
63
- GRAPHICS_DM_SUPPORT_ACTIVITIES_TIME_MENU = "Service & Support"
64
- GRAPHICS_AREA_DIV_ID = "graphics-area"
65
-
66
-
67
- def define_exported_column_names() -> Dict[str, str]:
68
- """
69
- Function to build the EXPORT_COLUMN_NAMES dict from colum names defined in global parameters
70
-
71
- :return: dict
72
- """
73
-
74
- global_params = GlobalParams()
75
- columns = global_params.columns
76
-
77
- return {
78
- columns["category"]: "Type d'activité",
79
- columns["fullname"]: "Agent",
80
- columns["hours"]: "Nombre d'heures",
81
- columns["masterproject"]: "Masterprojet",
82
- columns["team"]: "Equipe",
83
- columns["project"]: "Projet",
84
- }
85
-
86
-
87
- # Maps column names from queries to displayed column names in table/CSV
88
- EXPORT_COLUMN_NAMES = define_exported_column_names()
89
-
90
-
91
- def ijclab_team_export_table(team, team_selection_date, period_date: str, source):
92
- """
93
- Build the project list contributed by the selected team and the related time declarations and
94
- return a table.
95
-
96
- :param team: selected team
97
- :param team_selection_date: last time the team selection was changed
98
- :param period_date: a date that must be inside the declaration period
99
- :param source: whether to use Hito (non validated) or OSITAH (validated) as a data source
100
- :return: dbc.Table
101
- """
102
-
103
- if team is None:
104
- return html.Div("")
105
-
106
- global_params = GlobalParams()
107
- columns = global_params.columns
108
-
109
- start_date, end_date = get_validation_period_dates(period_date)
110
-
111
- projects_data, declaration_list = build_projects_data(
112
- team, team_selection_date, period_date, source
113
- )
114
- if projects_data is None or declaration_list is None:
115
- if source == DATA_SOURCE_HITO:
116
- msg = f"L'équipe '{team}' ne contribue à aucun projet"
117
- else:
118
- msg = f"Aucune données validées n'existe pour l'équipe '{team}'"
119
- msg += (
120
- f" pour la période du {start_date.strftime('%Y-%m-%d')} au"
121
- f" {end_date.strftime('%Y-%m-%d')}"
122
- )
123
- return html.Div([dbc.Alert(msg, color="warning"), add_source_selection_switch(source)])
124
-
125
- table_columns = [columns["masterproject"], columns["project"], columns["hours"]]
126
-
127
- table_header = [
128
- html.Thead(
129
- html.Tr(
130
- [
131
- *[
132
- html.Th(
133
- [
134
- html.I(f"{EXPORT_COLUMN_NAMES[c]} "),
135
- html.I(className="fas fa-sort mr-3"),
136
- ],
137
- className="text-center",
138
- )
139
- for c in table_columns
140
- ],
141
- ]
142
- )
143
- )
144
- ]
145
-
146
- table_body = [
147
- html.Tbody(
148
- [
149
- html.Tr(
150
- [
151
- html.Td(
152
- projects_data.iloc[i - 1][columns["masterproject"]],
153
- className="align-middle",
154
- key=f"analysis-table-cell-{i}-masterproject",
155
- ),
156
- html.Td(
157
- projects_data.iloc[i - 1][columns["project"]],
158
- className="align-middle",
159
- key=f"analysis-table-cell-{i}-project",
160
- ),
161
- html.Td(
162
- build_accordion(
163
- i,
164
- projects_data.iloc[i - 1][columns["hours"]],
165
- project_agents_time(
166
- declaration_list,
167
- projects_data.iloc[i - 1][columns["activity"]],
168
- ),
169
- f"{projects_data.iloc[i-1][columns['weeks']]} semaines",
170
- ),
171
- className="accordion",
172
- key=f"analysis-table-cell-{i}-time",
173
- ),
174
- ]
175
- )
176
- for i in range(1, len(projects_data) + 1)
177
- ]
178
- )
179
- ]
180
-
181
- if source == DATA_SOURCE_OSITAH:
182
- page_title = f"Contributions par projet validées de '{team}'"
183
- else:
184
- page_title = f"Contributions par projet déclarées (non validées) de '{team}'"
185
- page_title += f" du {start_date.strftime('%Y-%m-%d')} au {end_date.strftime('%Y-%m-%d')}"
186
-
187
- return html.Div(
188
- [
189
- html.Div(
190
- [
191
- dbc.Row(
192
- [
193
- dbc.Col(dbc.Alert(page_title), width=8),
194
- dbc.Col(
195
- [
196
- dbc.Button("Export CSV", id="ijclab-export-file-button"),
197
- dcc.Download(id="ijclab-export-file-download"),
198
- ],
199
- width={"size": 2, "offset": 2},
200
- ),
201
- ]
202
- ),
203
- add_source_selection_switch(source),
204
- ]
205
- ),
206
- html.P(""),
207
- dbc.Table(
208
- table_header + table_body,
209
- id={"type": TABLE_TYPE_TABLE, "id": TABLE_TEAM_PROJECTS_ID},
210
- bordered=True,
211
- hover=True,
212
- striped=True,
213
- class_name="sortable",
214
- ),
215
- ]
216
- )
217
-
218
-
219
- def ijclab_graphics(team, team_selection_date, period_date: str, source):
220
- """
221
- Build various graphics from declarations. This function just creates the basic structure of
222
- the graphic page and read the data. The actual graphic will be displayed by the callback
223
- associated with the dropdown menu used to select the graphics type.
224
-
225
- :param team: selected team
226
- :param team_selection_date: last time the team selection was changed
227
- :param period_date: a date that must be inside the declaration period
228
- :param source: whether to use Hito (non validated) or OSITAH (validated) as a data source
229
- :return: graphics and associated menus
230
- """
231
-
232
- if team is None:
233
- return html.Div("")
234
-
235
- start_date, end_date = get_validation_period_dates(period_date)
236
-
237
- projects_data, declaration_list = build_projects_data(
238
- team, team_selection_date, period_date, source
239
- )
240
- if projects_data is None or declaration_list is None:
241
- if source == DATA_SOURCE_HITO:
242
- msg = f"L'équipe '{team}' ne contribue à aucun projet"
243
- else:
244
- msg = f"Aucune données validées n'existe pour l'équipe '{team}'"
245
- msg += (
246
- f" pour la période du {start_date.strftime('%Y-%m-%d')} au"
247
- f" {end_date.strftime('%Y-%m-%d')}"
248
- )
249
- return html.Div([dbc.Alert(msg, color="warning"), add_source_selection_switch(source)])
250
-
251
- return html.Div(
252
- [
253
- dbc.Row(
254
- [
255
- dbc.Col(add_source_selection_switch(source), width=8),
256
- dbc.Col(graphics_dropdown_menu(), width={"size": 3, "offset": 1}),
257
- ]
258
- ),
259
- html.Div(id=GRAPHICS_AREA_DIV_ID),
260
- ]
261
- )
262
-
263
-
264
- def add_source_selection_switch(current_source):
265
- """
266
- Add a dbc.RadioItems to select the data source.
267
-
268
- :param current_source: currently selected source
269
- :return: dbc.RadioItems
270
- """
271
-
272
- return dbc.Row(
273
- [
274
- dbc.RadioItems(
275
- options=[
276
- {"label": "Toutes les déclarations", "value": DATA_SOURCE_HITO},
277
- {
278
- "label": "Déclarations validées uniquement",
279
- "value": DATA_SOURCE_OSITAH,
280
- },
281
- ],
282
- value=current_source,
283
- id=DATA_SELECTION_SOURCE_ID,
284
- inline=True,
285
- ),
286
- ],
287
- justify="center",
288
- )
289
-
290
-
291
- def project_agents_time(declarations, project):
292
- """
293
- Return a HTML Div with the list of agents who contributed to the project and their
294
- declared time.
295
-
296
- :param declarations: dataframe with the contribution of each agent to each project
297
- :param project: project fullname
298
- :return:
299
- """
300
-
301
- global_params = GlobalParams()
302
- columns = global_params.columns
303
-
304
- project_agents = declarations[declarations[columns["activity"]] == project].sort_values(
305
- by="nom"
306
- )
307
- project_agents[columns["hours"]] = np.round(project_agents[columns["hours"]]).astype("int")
308
- project_agents[columns["weeks"]] = np.round(project_agents[columns["hours"]] / WEEK_HOURS, 1)
309
- return html.Div(
310
- [
311
- html.Div(
312
- (
313
- f"{project_agents.iloc[i]['fullname']}:"
314
- f" {project_agents.iloc[i][columns['hours']]}"
315
- f" ({project_agents.iloc[i][columns['weeks']]} sem.)"
316
- )
317
- )
318
- for i in range(len(project_agents))
319
- ]
320
- )
321
-
322
-
323
- def graphics_dropdown_menu():
324
- """
325
- Build the dropdown menu to select the graphics type
326
-
327
- :return: dropdown menu
328
- """
329
-
330
- return dbc.DropdownMenu(
331
- [
332
- dbc.DropdownMenuItem(
333
- GRAPHICS_DM_CATEGORY_TIME_MENU,
334
- id=GRAPHICS_DM_CATEGORY_TIME_ID,
335
- n_clicks=0,
336
- ),
337
- dbc.DropdownMenuItem(divider=True),
338
- dbc.DropdownMenuItem(
339
- GRAPHICS_DM_NSIP_PROJECTS_TIME_MENU,
340
- id=GRAPHICS_DM_NSIP_PROJECTS_TIME_ID,
341
- n_clicks=0,
342
- ),
343
- dbc.DropdownMenuItem(
344
- GRAPHICS_DM_LOCAL_PROJECTS_TIME_MENU,
345
- id=GRAPHICS_DM_LOCAL_PROJECTS_TIME_ID,
346
- n_clicks=0,
347
- ),
348
- dbc.DropdownMenuItem(divider=True),
349
- dbc.DropdownMenuItem(
350
- GRAPHICS_DM_TEACHING_ACTIVITIES_TIME_MENU,
351
- id=GRAPHICS_DM_TEACHING_ACTIVITIES_TIME_ID,
352
- n_clicks=0,
353
- ),
354
- dbc.DropdownMenuItem(
355
- GRAPHICS_DM_CONSULTANCY_ACTIVITIES_TIME_MENU,
356
- id=GRAPHICS_DM_CONSULTANCY_ACTIVITIES_TIME_ID,
357
- n_clicks=0,
358
- ),
359
- dbc.DropdownMenuItem(
360
- GRAPHICS_DM_SUPPORT_ACTIVITIES_TIME_MENU,
361
- id=GRAPHICS_DM_SUPPORT_ACTIVITIES_TIME_ID,
362
- n_clicks=0,
363
- ),
364
- ],
365
- id=GRAPHICS_DROPDOWN_ID,
366
- label=GRAPHICS_DROPDOWN_MENU,
367
- )
368
-
369
-
370
- def analysis_submenus():
371
- """
372
- Build the tabs menus of the export subapplication
373
-
374
- :return: DBC Tabs
375
- """
376
-
377
- return dbc.Tabs(
378
- [
379
- dbc.Tab(
380
- id=TAB_ID_ANALYSIS_IJCLAB,
381
- tab_id=TAB_ID_ANALYSIS_IJCLAB,
382
- label=TAB_MENU_ANALYSIS_IJCLAB,
383
- ),
384
- dbc.Tab(
385
- id=TAB_ID_ANALYSIS_GRAPHICS,
386
- tab_id=TAB_ID_ANALYSIS_GRAPHICS,
387
- label=TAB_MENU_ANALYSIS_GRAPHICS,
388
- ),
389
- ],
390
- id=ANALYSIS_TAB_MENU_ID,
391
- )
392
-
393
-
394
- def analysis_layout():
395
- """
396
- Build the layout for this application, after reading the data if necessary.
397
-
398
- :return: application layout
399
- """
400
-
401
- return html.Div(
402
- [
403
- html.H1("Analyse des déclarations"),
404
- team_list_dropdown(),
405
- # The following dcc.Store is used to ensure that the the ijclab_export input exists
406
- # before the export page is created
407
- dcc.Store(id=DATA_SELECTED_SOURCE_ID, data=DATA_SOURCE_HITO),
408
- html.Div(analysis_submenus(), id="analysis-submenus", style={"margin-top": "3em"}),
409
- dcc.Store(id=ANALYSIS_LOAD_INDICATOR_ID, data=0),
410
- dcc.Store(id=ANALYSIS_SAVED_INDICATOR_ID, data=0),
411
- dcc.Store(id=ANALYSIS_SAVED_ACTIVE_TAB_ID, data=""),
412
- dcc.Store(
413
- id={"type": TABLE_TYPE_DUMMY_STORE, "id": TABLE_TEAM_PROJECTS_ID},
414
- data=0,
415
- ),
416
- ]
417
- )
418
-
419
-
420
- @app.callback(
421
- Output(DATA_SELECTED_SOURCE_ID, "data"),
422
- Input(DATA_SELECTION_SOURCE_ID, "value"),
423
- State(DATA_SELECTED_SOURCE_ID, "data"),
424
- prevent_initial_call=True,
425
- )
426
- def select_data_source(new_source, previous_source):
427
- """
428
- This callback is used to forward to the export callback the selected source through a
429
- dcc.Store that exists before the page is created. It also clears the data cache if
430
- the source has been changed.
431
-
432
- :param new_source: value to forward to the dcc.Store
433
- :param previous_source: previous value of the selection
434
- :return: new_source value
435
- """
436
-
437
- if new_source != previous_source:
438
- clear_cached_data()
439
-
440
- return new_source
441
-
442
-
443
- @app.callback(
444
- [
445
- Output(TAB_ID_ANALYSIS_IJCLAB, "children"),
446
- Output(TAB_ID_ANALYSIS_GRAPHICS, "children"),
447
- Output(ANALYSIS_SAVED_INDICATOR_ID, "data"),
448
- Output(ANALYSIS_SAVED_ACTIVE_TAB_ID, "data"),
449
- ],
450
- [
451
- Input(ANALYSIS_LOAD_INDICATOR_ID, "data"),
452
- Input(ANALYSIS_TAB_MENU_ID, "active_tab"),
453
- Input(TEAM_SELECTED_VALUE_ID, "data"),
454
- Input(DATA_SELECTED_SOURCE_ID, "data"),
455
- ],
456
- [
457
- State(TEAM_SELECTION_DATE_ID, "data"),
458
- State(ANALYSIS_SAVED_INDICATOR_ID, "data"),
459
- State(VALIDATION_PERIOD_SELECTED_ID, "data"),
460
- State(ANALYSIS_SAVED_ACTIVE_TAB_ID, "data"),
461
- ],
462
- prevent_initial_call=True,
463
- )
464
- def display_analysis_tables(
465
- load_in_progress,
466
- active_tab,
467
- team,
468
- data_source,
469
- team_selection_date,
470
- previous_load_in_progress,
471
- period_date: str,
472
- previous_active_tab,
473
- ):
474
- """
475
- Display active tab contents after a team or an active tab change. Exact action depends on the
476
- value of the load in progress indicator. If it is equal to the previous value, it means this
477
- is the start of the update process: progress bar is displayed and a dcc.Interval is created
478
- to schedule again this callback after incrementing the load in progress indicator. This causes
479
- the callback to be reentered and this time it triggers the real processing for the tab
480
- resulting in the final update of the active tab contents. An empty content is returned for
481
- inactive tabs.
482
-
483
- :param load_in_progress: load in progress indicator
484
- :param tab: tab name
485
- :param team: selected team
486
- :param data_source: Hito (non-validated declarations) or OSITAH (validated declarations)
487
- :param team_selection_date: last time the team selection was changed
488
- :param previous_load_in_progress: previous value of the load_in_progress indicator
489
- :param period_date: a date that must be inside the declaration period
490
- :param previous_active_tab: previously active tab
491
- :return: tab content
492
- """
493
-
494
- tab_contents = []
495
-
496
- # Be sure to fill the return values in the same order as Output are declared
497
- tab_list = [TAB_ID_ANALYSIS_IJCLAB, TAB_ID_ANALYSIS_GRAPHICS]
498
- for tab in tab_list:
499
- if team and len(team) > 0 and tab == active_tab:
500
- if load_in_progress > previous_load_in_progress and active_tab == previous_active_tab:
501
- if tab == TAB_ID_ANALYSIS_IJCLAB:
502
- tab_contents.append(
503
- ijclab_team_export_table(
504
- team, team_selection_date, period_date, data_source
505
- )
506
- )
507
- elif tab == TAB_ID_ANALYSIS_GRAPHICS:
508
- tab_contents.append(
509
- ijclab_graphics(team, team_selection_date, period_date, data_source)
510
- )
511
- else:
512
- tab_contents.append(
513
- dbc.Alert("Erreur interne: tab non supporté"), color="warning"
514
- )
515
- previous_load_in_progress += 1
516
- else:
517
- component = html.Div(
518
- [
519
- create_progress_bar(team, duration=ANALYSIS_PROGRESS_BAR_MAX_DURATION),
520
- dcc.Interval(
521
- id=ANALYSIS_TRIGGER_INTERVAL_ID,
522
- n_intervals=0,
523
- max_intervals=1,
524
- interval=500,
525
- ),
526
- ]
527
- )
528
- tab_contents.append(component)
529
- else:
530
- tab_contents.append("")
531
-
532
- tab_contents.extend([previous_load_in_progress, active_tab])
533
-
534
- return tab_contents
535
-
536
-
537
- @app.callback(
538
- Output(ANALYSIS_LOAD_INDICATOR_ID, "data"),
539
- Input(ANALYSIS_TRIGGER_INTERVAL_ID, "n_intervals"),
540
- State(ANALYSIS_SAVED_INDICATOR_ID, "data"),
541
- prevent_initial_call=True,
542
- )
543
- def display_tables_trigger(n, previous_load_indicator):
544
- """
545
- Increment (change) of the input of display_tables_trigger callback to get it fired a
546
- second time after displaying the progress bar. The output component must be updated each
547
- time the callback is entered to trigger the execution of the other callback, thus the
548
- choice of incrementing it at each call.
549
-
550
- :param n: n_interval property of the dcc.Interval (0 or 1)
551
- :return: 1 increment to previous value
552
- """
553
-
554
- return previous_load_indicator + 1
555
-
556
-
557
- @app.callback(
558
- Output("ijclab-export-file-download", "data"),
559
- Input("ijclab-export-file-button", "n_clicks"),
560
- [
561
- State(TEAM_SELECTED_VALUE_ID, "data"),
562
- State(TEAM_SELECTION_DATE_ID, "data"),
563
- State(DATA_SELECTED_SOURCE_ID, "data"),
564
- State(VALIDATION_PERIOD_SELECTED_ID, "data"),
565
- ],
566
- prevent_initial_call=True,
567
- )
568
- def ijclab_export_to_csv(_, team, team_selection_date, source, period_date):
569
- """
570
- Generate a CSV file for the selected team, using the appropriate data source.
571
-
572
- :param _: unused, just an input to trigger the callback
573
- :param team: selected team
574
- :param team_selection_date: timestamp of the last change in team selection
575
- :param period_date: a date that must be inside the declaration period
576
- :return: None
577
- """
578
-
579
- global_params = GlobalParams()
580
- columns = global_params.columns
581
-
582
- declaration_list = get_team_projects(team, team_selection_date, period_date, source)
583
- if declaration_list is None:
584
- return dbc.Alert(
585
- f"L'équipe '{team}' ne contribue à aucun projet actuellement",
586
- color="warning",
587
- )
588
-
589
- exported_data = declaration_list[
590
- [
591
- columns["masterproject"],
592
- columns["project"],
593
- columns["category"],
594
- columns["fullname"],
595
- columns["team"],
596
- columns["hours"],
597
- ]
598
- ]
599
- exported_data[columns["hours"]] = np.round(exported_data[columns["hours"]]).astype("int")
600
- column_renames = {}
601
- for c in exported_data.columns.tolist():
602
- if c in EXPORT_COLUMN_NAMES:
603
- column_renames[c] = EXPORT_COLUMN_NAMES[c]
604
- if len(column_renames.keys()) > 0:
605
- exported_data.rename(columns=column_renames, inplace=True)
606
-
607
- return dict(
608
- content=exported_data.to_csv(index=False, sep=";"),
609
- filename="project_contributions.csv",
610
- )
611
-
612
-
613
- @app.callback(
614
- Output(GRAPHICS_AREA_DIV_ID, "children"),
615
- Output(GRAPHICS_DROPDOWN_ID, "label"),
616
- Output(GRAPHICS_AREA_DIV_ID, "style"),
617
- Input(GRAPHICS_DM_CATEGORY_TIME_ID, "n_clicks"),
618
- Input(GRAPHICS_DM_NSIP_PROJECTS_TIME_ID, "n_clicks"),
619
- Input(GRAPHICS_DM_LOCAL_PROJECTS_TIME_ID, "n_clicks"),
620
- Input(GRAPHICS_DM_TEACHING_ACTIVITIES_TIME_ID, "n_clicks"),
621
- Input(GRAPHICS_DM_CONSULTANCY_ACTIVITIES_TIME_ID, "n_clicks"),
622
- Input(GRAPHICS_DM_SUPPORT_ACTIVITIES_TIME_ID, "n_clicks"),
623
- State(TEAM_SELECTED_VALUE_ID, "data"),
624
- State(TEAM_SELECTION_DATE_ID, "data"),
625
- State(VALIDATION_PERIOD_SELECTED_ID, "data"),
626
- State(DATA_SELECTED_SOURCE_ID, "data"),
627
- State(GRAPHICS_DROPDOWN_ID, "label"),
628
- prevent_initial_call=True,
629
- )
630
- def display_graphics(
631
- _1,
632
- _2,
633
- _3,
634
- _4,
635
- _5,
636
- _6,
637
- team,
638
- team_selection_date,
639
- period_date,
640
- data_source,
641
- dropdown_label,
642
- ):
643
- """
644
- Display the selected graphics type
645
-
646
- :param _n: n_clicks property for each menu item used as input
647
- :param team: selected team
648
- :param team_selection_date: last time the team selection was changed
649
- :param period_date: a date that must be inside the declaration period
650
- :param data_source: Hito (non-validated declarations) or OSITAH (validated declarations)
651
- :param dropdown_label: Dropdown menu label
652
- :return: dcc.Graph
653
- """
654
-
655
- global_params = GlobalParams()
656
- columns = global_params.columns
657
-
658
- ctx = dash.callback_context
659
- if not ctx.triggered:
660
- raise PreventUpdate
661
- else:
662
- selected_item = ctx.triggered[0]["prop_id"].split(".")[0]
663
-
664
- projects_data, _ = build_projects_data(team, team_selection_date, period_date, data_source)
665
-
666
- if selected_item in [
667
- GRAPHICS_DM_NSIP_PROJECTS_TIME_ID,
668
- GRAPHICS_DM_LOCAL_PROJECTS_TIME_ID,
669
- GRAPHICS_DM_SUPPORT_ACTIVITIES_TIME_ID,
670
- ]:
671
- if selected_item == GRAPHICS_DM_NSIP_PROJECTS_TIME_ID:
672
- activity_data = projects_data.loc[projects_data[columns["category"]] == "nsip_project"]
673
- fig_title = "Temps par masterprojet et projet NSIP"
674
- y_column = columns["masterproject"]
675
- new_dropdown_label = GRAPHICS_DM_NSIP_PROJECTS_TIME_MENU
676
- elif selected_item == GRAPHICS_DM_LOCAL_PROJECTS_TIME_ID:
677
- activity_data = projects_data.loc[projects_data[columns["category"]] == "local_project"]
678
- fig_title = "Temps par projet local"
679
- y_column = "project_short"
680
- new_dropdown_label = GRAPHICS_DM_LOCAL_PROJECTS_TIME_MENU
681
- elif selected_item == GRAPHICS_DM_SUPPORT_ACTIVITIES_TIME_ID:
682
- activity_data = projects_data.loc[projects_data[columns["category"]] == "service"]
683
- fig_title = "Activités de Service & Support"
684
- y_column = "project_short"
685
- new_dropdown_label = GRAPHICS_DM_SUPPORT_ACTIVITIES_TIME_MENU
686
- else:
687
- return general_error_jumbotron(
688
- f"Erreur interne : '{selected_item}' non supporté pour un graphique en barre"
689
- )
690
-
691
- bar_num = len(activity_data[columns["project"]].unique())
692
- fig_height = "calc(100vh - 300px)"
693
-
694
- if activity_data.empty:
695
- fig = None
696
- fig_area_style = None
697
- else:
698
- fig = px.bar(
699
- activity_data,
700
- x=columns["hours"],
701
- y=y_column,
702
- color=columns["project"],
703
- orientation="h",
704
- height=200 + (30 * bar_num),
705
- title=fig_title,
706
- )
707
- fig.update_layout(
708
- showlegend=False,
709
- yaxis={"categoryorder": "category descending"},
710
- )
711
- fig_area_style = {
712
- "max-height": fig_height,
713
- "overflow-y": "scroll",
714
- "position": "relative",
715
- }
716
-
717
- elif selected_item in [
718
- GRAPHICS_DM_CATEGORY_TIME_ID,
719
- GRAPHICS_DM_TEACHING_ACTIVITIES_TIME_ID,
720
- GRAPHICS_DM_CONSULTANCY_ACTIVITIES_TIME_ID,
721
- ]:
722
- if selected_item == GRAPHICS_DM_CATEGORY_TIME_ID:
723
- activity_data = projects_data
724
- fig_title = "Temps par catégorie d'activités"
725
- y_column = columns["category"]
726
- new_dropdown_label = GRAPHICS_DM_CATEGORY_TIME_MENU
727
- elif selected_item == GRAPHICS_DM_TEACHING_ACTIVITIES_TIME_ID:
728
- activity_data = projects_data.loc[projects_data[columns["category"]] == "enseignement"]
729
- fig_title = "Activités d'enseignement"
730
- y_column = columns["project"]
731
- new_dropdown_label = GRAPHICS_DM_TEACHING_ACTIVITIES_TIME_MENU
732
- elif selected_item == GRAPHICS_DM_CONSULTANCY_ACTIVITIES_TIME_ID:
733
- activity_data = projects_data.loc[projects_data[columns["category"]] == "consultance"]
734
- fig_title = "Activités de Consultance et Expertise"
735
- y_column = columns["project"]
736
- new_dropdown_label = GRAPHICS_DM_CONSULTANCY_ACTIVITIES_TIME_MENU
737
- else:
738
- return general_error_jumbotron(
739
- f"Erreur interne : '{selected_item}' non supporté pour un graphique en barre"
740
- )
741
-
742
- fig_area_style = None
743
- fig_height = 400
744
-
745
- if activity_data.empty:
746
- fig = None
747
- else:
748
- fig = px.pie(
749
- activity_data,
750
- values=columns["hours"],
751
- names=y_column,
752
- title=fig_title,
753
- height=fig_height,
754
- )
755
-
756
- else:
757
- return (
758
- general_error_jumbotron(f"Graphics type '{selected_item}' not yet implemented"),
759
- dropdown_label,
760
- None,
761
- )
762
-
763
- if fig is None:
764
- return (
765
- dbc.Alert(f"Aucune activité correspondant à {new_dropdown_label}", color="warning"),
766
- dropdown_label,
767
- fig_area_style,
768
- )
769
- else:
770
- return (
771
- dcc.Graph("graphics-figure", figure=fig),
772
- new_dropdown_label,
773
- fig_area_style,
774
- )