ositah 23.8.dev1__tar.gz → 24.2.dev1__tar.gz
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.
- {ositah-23.8.dev1 → ositah-24.2.dev1}/.gitlab-ci.yml +5 -5
- {ositah-23.8.dev1/ositah.egg-info → ositah-24.2.dev1}/PKG-INFO +15 -1
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/configuration/main.py +5 -3
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/export.py +38 -30
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/validation/callbacks.py +3 -2
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/validation/tables.py +52 -19
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/main.py +11 -12
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/exceptions.py +64 -52
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/menus.py +34 -3
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/projects.py +26 -19
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/utils.py +3 -3
- {ositah-23.8.dev1 → ositah-24.2.dev1/ositah.egg-info}/PKG-INFO +15 -1
- {ositah-23.8.dev1 → ositah-24.2.dev1}/pyproject.toml +1 -1
- {ositah-23.8.dev1 → ositah-24.2.dev1}/.gitignore +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/LICENSE +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/README.md +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/gunicorn.config/README.md +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/gunicorn.config/gunicorn.ositah +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/gunicorn.config/gunicorn@.service +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/gunicorn.config/ositah.conf.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/noxfile.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/__init__.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/app.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/__init__.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/analysis.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/configuration/__init__.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/configuration/callbacks.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/configuration/parameters.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/configuration/tools.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/validation/__init__.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/validation/main.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/validation/parameters.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/apps/validation/tools.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/assets/arrow_down_up.svg +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/assets/ositah.css +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/assets/sort_ascending.svg +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/assets/sort_descending.svg +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/assets/sorttable.js +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/ositah.example.cfg +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/static/style.css +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/templates/base.html +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/templates/bootstrap_login.html +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/templates/login_form.html +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/__init__.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/agents.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/authentication.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/cache.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/core.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/hito_db.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/hito_db_model.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/period.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah/utils/teams.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah.egg-info/SOURCES.txt +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah.egg-info/dependency_links.txt +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah.egg-info/entry_points.txt +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah.egg-info/requires.txt +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/ositah.egg-info/top_level.txt +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/setup.cfg +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/setup.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/README.md +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/accordion.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/authentication.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/checkbox.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/checklist.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/file-selector.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/file-upload.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/long_running_callback.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/pandas_split.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/pandas_split_bug_report.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/pattern-matching-callback.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/progess_bar.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/reset_table_checkboxes.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/sortable_table.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/sqlalchemy_test.py +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/templates/base.html +0 -0
- {ositah-23.8.dev1 → ositah-24.2.dev1}/test-dash/templates/login_form.html +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
default:
|
|
2
|
-
tags:
|
|
3
|
-
# Ensure that the pipeline is executed in IJCLab intranet - TO BE CHANGED
|
|
4
|
-
- oval-checks
|
|
5
|
-
|
|
6
1
|
stages:
|
|
7
2
|
- build
|
|
8
3
|
|
|
9
4
|
build:
|
|
10
5
|
stage: build
|
|
11
6
|
script:
|
|
7
|
+
- apk add --update python3
|
|
8
|
+
- python --version
|
|
9
|
+
- python -m venv .venv
|
|
10
|
+
- . .venv/bin/activate
|
|
11
|
+
- python -m ensurepip
|
|
12
12
|
- pip install nox pyproject-parser consolekit
|
|
13
13
|
- nox -s lint
|
|
14
14
|
- pyproject-parser check
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ositah
|
|
3
|
-
Version:
|
|
3
|
+
Version: 24.2.dev1
|
|
4
4
|
Summary: Outils de Suivi d'Activités basé sur Hito
|
|
5
5
|
Author-email: Michel Jouvin <michel.jouvin@ijclab.in2p3.fr>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -12,6 +12,20 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.8
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
+
Requires-Dist: blinker
|
|
16
|
+
Requires-Dist: dash
|
|
17
|
+
Requires-Dist: dash-bootstrap-components
|
|
18
|
+
Requires-Dist: flask~=2.2
|
|
19
|
+
Requires-Dist: flask-multipass
|
|
20
|
+
Requires-Dist: flask-sqlalchemy~=3.0
|
|
21
|
+
Requires-Dist: flask-wtf
|
|
22
|
+
Requires-Dist: hito-tools>=23.2
|
|
23
|
+
Requires-Dist: pandas==2.*
|
|
24
|
+
Requires-Dist: pymysql
|
|
25
|
+
Requires-Dist: python-ldap
|
|
26
|
+
Requires-Dist: pyyaml
|
|
27
|
+
Requires-Dist: simplejson
|
|
28
|
+
Requires-Dist: sqlalchemy~=2.0
|
|
15
29
|
|
|
16
30
|
# OSITAH : Outil de Suivi de Temps et d'Activités basé sur Hito
|
|
17
31
|
|
|
@@ -197,9 +197,11 @@ def declaration_periods_layout():
|
|
|
197
197
|
width={"size": 3, "offset": 0},
|
|
198
198
|
),
|
|
199
199
|
dbc.Tooltip(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
(
|
|
201
|
+
"Le semestre courant existe déjà"
|
|
202
|
+
if current_period_exists
|
|
203
|
+
else "Ajoute le semestre courant"
|
|
204
|
+
),
|
|
203
205
|
target=DECLARATION_PERIODS_CREATE_DIV_ID,
|
|
204
206
|
placement="bottom",
|
|
205
207
|
),
|
|
@@ -338,10 +338,14 @@ def nsip_export_table(team, team_selection_date, period_date: str, declarations_
|
|
|
338
338
|
declaration_list.loc[
|
|
339
339
|
declaration_list["_merge"] == "both", "mgr_val_time_mismatch"
|
|
340
340
|
] = declaration_list[declaration_list["_merge"] == "both"].apply(
|
|
341
|
-
lambda r:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
341
|
+
lambda r: (
|
|
342
|
+
False
|
|
343
|
+
if r["managerValidationDate"]
|
|
344
|
+
and re.match(
|
|
345
|
+
r["managerValidationDate"], r["validation_time"].date().isoformat()
|
|
346
|
+
)
|
|
347
|
+
else True
|
|
348
|
+
),
|
|
345
349
|
axis=1,
|
|
346
350
|
)
|
|
347
351
|
# nsip_missing is True if the OSITAH declaration has no matching declaration in NSIP
|
|
@@ -358,9 +362,9 @@ def nsip_export_table(team, team_selection_date, period_date: str, declarations_
|
|
|
358
362
|
"time": "time_nsipd",
|
|
359
363
|
"time_unit": "volume",
|
|
360
364
|
}.items():
|
|
361
|
-
declaration_list.loc[
|
|
362
|
-
declaration_list[
|
|
363
|
-
|
|
365
|
+
declaration_list.loc[declaration_list["ositah_missing"], ositah_column] = (
|
|
366
|
+
declaration_list[nsip_column]
|
|
367
|
+
)
|
|
364
368
|
# Fix nsip_project if it was set to the project name when the activity is a reference
|
|
365
369
|
declaration_list.loc[
|
|
366
370
|
declaration_list["nsip_project"].isna()
|
|
@@ -416,9 +420,9 @@ def nsip_export_table(team, team_selection_date, period_date: str, declarations_
|
|
|
416
420
|
# Rset nsip_project_id and nsip_reference_id to NaN if they are equal to 0 so that the
|
|
417
421
|
# corresponding cell is empty
|
|
418
422
|
declaration_list.loc[declaration_list["nsip_project_id"] == 0, "nsip_project_id"] = np.NaN
|
|
419
|
-
declaration_list.loc[
|
|
420
|
-
|
|
421
|
-
|
|
423
|
+
declaration_list.loc[declaration_list["nsip_reference_id"] == 0, "nsip_reference_id"] = (
|
|
424
|
+
np.NaN
|
|
425
|
+
)
|
|
422
426
|
|
|
423
427
|
# Sort declarations by email_auth and add index value as column for easier further
|
|
424
428
|
# processing
|
|
@@ -483,9 +487,11 @@ def nsip_export_table(team, team_selection_date, period_date: str, declarations_
|
|
|
483
487
|
[
|
|
484
488
|
html.Th(
|
|
485
489
|
[
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
490
|
+
(
|
|
491
|
+
dbc.Checkbox(id=NSIP_EXPORT_SELECT_ALL_ID)
|
|
492
|
+
if selected_declarations["selectable"].any()
|
|
493
|
+
else html.Div()
|
|
494
|
+
),
|
|
489
495
|
dcc.Store(id=NSIP_EXPORT_ALL_SELECTED_ID, data=0),
|
|
490
496
|
]
|
|
491
497
|
),
|
|
@@ -560,23 +566,25 @@ def nsip_build_user_declarations(declarations, agent_email, data_columns):
|
|
|
560
566
|
# (rowSpan is in fact the number of Tr following this one attached to it)
|
|
561
567
|
html.Tr(
|
|
562
568
|
[
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
569
|
+
(
|
|
570
|
+
html.Td(
|
|
571
|
+
[
|
|
572
|
+
dbc.Checkbox(
|
|
573
|
+
id={"type": "nsip-export-user", "id": agent_email},
|
|
574
|
+
class_name="nsip-agent-selector",
|
|
575
|
+
value=False,
|
|
576
|
+
),
|
|
577
|
+
dcc.Store(
|
|
578
|
+
id={"type": "nsip-export-user-selected", "id": agent_email},
|
|
579
|
+
data=0,
|
|
580
|
+
),
|
|
581
|
+
],
|
|
582
|
+
className="align-middle ",
|
|
583
|
+
rowSpan=len(user_declarations) + 1,
|
|
584
|
+
)
|
|
585
|
+
if user_declarations["selectable"].any()
|
|
586
|
+
else html.Td(rowSpan=len(user_declarations) + 1)
|
|
587
|
+
),
|
|
580
588
|
nsip_build_poject_declaration_cell(user_declarations, "email_auth", None),
|
|
581
589
|
dcc.Store(
|
|
582
590
|
id={"type": "nsip-selected-user", "id": agent_email},
|
|
@@ -196,8 +196,9 @@ def validation_button_callback(value, agent_id, team, team_selection_date, perio
|
|
|
196
196
|
session.add(validation_data)
|
|
197
197
|
# A flush() is required before calling project_declaration_snapshot() else the
|
|
198
198
|
# first insert is lost
|
|
199
|
-
# MJ 2023-07-12: hack to workaround a problem with flush() leading to an undefined
|
|
200
|
-
#
|
|
199
|
+
# MJ 2023-07-12: hack to workaround a problem with flush() leading to an undefined
|
|
200
|
+
# foreign key
|
|
201
|
+
# session.flush()
|
|
201
202
|
session.commit()
|
|
202
203
|
project_declaration_snapshot(
|
|
203
204
|
agent_id,
|
|
@@ -27,8 +27,8 @@ from ositah.apps.validation.tools import (
|
|
|
27
27
|
validation_started,
|
|
28
28
|
)
|
|
29
29
|
from ositah.utils.agents import get_agents
|
|
30
|
-
from ositah.utils.exceptions import SessionDataMissing
|
|
31
|
-
from ositah.utils.menus import TABLE_TYPE_TABLE, build_accordion
|
|
30
|
+
from ositah.utils.exceptions import InvalidHitoProjectName, SessionDataMissing
|
|
31
|
+
from ositah.utils.menus import TABLE_TYPE_TABLE, build_accordion, ositah_jumbotron
|
|
32
32
|
from ositah.utils.projects import (
|
|
33
33
|
CATEGORY_DEFAULT,
|
|
34
34
|
DATA_SOURCE_HITO,
|
|
@@ -72,9 +72,17 @@ def build_validation_table(team, team_selection_date, declaration_set: int, peri
|
|
|
72
72
|
|
|
73
73
|
validation_data = get_all_validation_status(period_date)
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
try:
|
|
76
|
+
project_declarations = get_team_projects(
|
|
77
|
+
team, team_selection_date, period_date, DATA_SOURCE_HITO
|
|
78
|
+
)
|
|
79
|
+
except InvalidHitoProjectName as e:
|
|
80
|
+
return ositah_jumbotron(
|
|
81
|
+
"Error loading projects",
|
|
82
|
+
e.msg,
|
|
83
|
+
title_class="text-danger",
|
|
84
|
+
)
|
|
85
|
+
|
|
78
86
|
if project_declarations is None:
|
|
79
87
|
return html.Div(
|
|
80
88
|
[
|
|
@@ -95,9 +103,17 @@ def build_validation_table(team, team_selection_date, declaration_set: int, peri
|
|
|
95
103
|
data_columns = [CATEGORY_DEFAULT]
|
|
96
104
|
data_columns.extend(global_params.project_categories.keys())
|
|
97
105
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
try:
|
|
107
|
+
validated_project_declarations = get_team_projects(
|
|
108
|
+
team, team_selection_date, period_date, DATA_SOURCE_OSITAH, use_cache=False
|
|
109
|
+
)
|
|
110
|
+
except InvalidHitoProjectName as e:
|
|
111
|
+
return ositah_jumbotron(
|
|
112
|
+
"Error loading projects",
|
|
113
|
+
e.msg,
|
|
114
|
+
title_class="text-danger",
|
|
115
|
+
)
|
|
116
|
+
|
|
101
117
|
if validated_project_declarations is None:
|
|
102
118
|
declaration_list["validated"] = False
|
|
103
119
|
declaration_list["hito_missing"] = False
|
|
@@ -127,12 +143,12 @@ def build_validation_table(team, team_selection_date, declaration_set: int, peri
|
|
|
127
143
|
declaration_list.loc[declaration_list["hito_missing"]] = declaration_list.fillna(
|
|
128
144
|
value=columns_fillna
|
|
129
145
|
)
|
|
130
|
-
declaration_list.loc[
|
|
131
|
-
declaration_list["
|
|
132
|
-
|
|
133
|
-
declaration_list.loc[
|
|
134
|
-
declaration_list["
|
|
135
|
-
|
|
146
|
+
declaration_list.loc[declaration_list["hito_missing"], column_names["fullname"]] = (
|
|
147
|
+
declaration_list[f"{column_names['fullname']}_val"]
|
|
148
|
+
)
|
|
149
|
+
declaration_list.loc[declaration_list["hito_missing"], column_names["team"]] = (
|
|
150
|
+
declaration_list[f"{column_names['team']}_val"]
|
|
151
|
+
)
|
|
136
152
|
declaration_list.loc[declaration_list["hito_missing"], "suspect"] = True
|
|
137
153
|
declaration_list.loc[declaration_list["hito_missing"], "validation_disabled"] = True
|
|
138
154
|
declaration_list.sort_values(by=column_names["fullname"], inplace=True)
|
|
@@ -203,9 +219,11 @@ def build_validation_table(team, team_selection_date, declaration_set: int, peri
|
|
|
203
219
|
selected_declarations.iloc[i - 1][column_names["fullname"]]
|
|
204
220
|
),
|
|
205
221
|
agent_tooltip_txt(selected_declarations.iloc[i - 1], data_columns),
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
(
|
|
223
|
+
"validated_hito_missing"
|
|
224
|
+
if selected_declarations.iloc[i - 1]["hito_missing"]
|
|
225
|
+
else ""
|
|
226
|
+
),
|
|
209
227
|
),
|
|
210
228
|
className="accordion",
|
|
211
229
|
key=f"validation-table-cell-{i}-fullname",
|
|
@@ -314,7 +332,14 @@ def build_missing_agents_table(team, team_selection_date, period_date: str):
|
|
|
314
332
|
column_names = global_params.columns
|
|
315
333
|
declaration_options = global_params.declaration_options
|
|
316
334
|
|
|
317
|
-
|
|
335
|
+
try:
|
|
336
|
+
declarations = get_team_projects(team, team_selection_date, period_date)
|
|
337
|
+
except InvalidHitoProjectName as e:
|
|
338
|
+
return ositah_jumbotron(
|
|
339
|
+
"Error loading projects",
|
|
340
|
+
e.msg,
|
|
341
|
+
title_class="text-danger",
|
|
342
|
+
)
|
|
318
343
|
|
|
319
344
|
agents = get_agents(period_date)
|
|
320
345
|
|
|
@@ -425,7 +450,15 @@ def build_statistics_table(team, team_selection_date, period_date: str):
|
|
|
425
450
|
except SessionDataMissing:
|
|
426
451
|
return no_session_id_jumbotron()
|
|
427
452
|
|
|
428
|
-
|
|
453
|
+
try:
|
|
454
|
+
declarations = get_team_projects(team, team_selection_date, period_date)
|
|
455
|
+
except InvalidHitoProjectName as e:
|
|
456
|
+
return ositah_jumbotron(
|
|
457
|
+
"Error loading projects",
|
|
458
|
+
e.msg,
|
|
459
|
+
title_class="text-danger",
|
|
460
|
+
)
|
|
461
|
+
|
|
429
462
|
agents = get_agents(period_date)
|
|
430
463
|
if team == TEAM_LIST_ALL_AGENTS:
|
|
431
464
|
agent_list = agents
|
|
@@ -33,6 +33,7 @@ from ositah.utils.menus import (
|
|
|
33
33
|
TEAM_SELECTED_VALUE_ID,
|
|
34
34
|
TEAM_SELECTION_DATE_ID,
|
|
35
35
|
VALIDATION_PERIOD_SELECTED_ID,
|
|
36
|
+
ositah_jumbotron,
|
|
36
37
|
)
|
|
37
38
|
from ositah.utils.utils import (
|
|
38
39
|
AUTHORIZED_ROLES,
|
|
@@ -104,23 +105,21 @@ def default_config_path() -> str:
|
|
|
104
105
|
return config_file
|
|
105
106
|
|
|
106
107
|
|
|
107
|
-
# URL not found
|
|
108
|
+
# URL not found jumbotron
|
|
108
109
|
def url_not_found(path):
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
html.P(f"URL {path} was not recognised..."),
|
|
114
|
-
]
|
|
110
|
+
return ositah_jumbotron(
|
|
111
|
+
"404: Not found",
|
|
112
|
+
f"URL {path} was not recognised...",
|
|
113
|
+
title_class="text-danger",
|
|
115
114
|
)
|
|
116
115
|
|
|
117
116
|
|
|
118
|
-
#
|
|
117
|
+
# valid role missing jumbotron
|
|
119
118
|
def valid_role_missing(msg):
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
return ositah_jumbotron(
|
|
120
|
+
"You don't havea valid Hito role to OSITAH",
|
|
121
|
+
msg,
|
|
122
|
+
title_class="text-warning",
|
|
124
123
|
)
|
|
125
124
|
|
|
126
125
|
|
|
@@ -1,52 +1,64 @@
|
|
|
1
|
-
# OSITAH exceptions not specific to one of the sub-application
|
|
2
|
-
|
|
3
|
-
from hito_tools.exceptions import EXIT_STATUS_GENERAL_ERROR
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class InvalidCallbackInput(Exception):
|
|
7
|
-
def __init__(self, input_name):
|
|
8
|
-
self.msg = f"internal error: invalid input ({input_name}) in callback"
|
|
9
|
-
self.status = EXIT_STATUS_GENERAL_ERROR
|
|
10
|
-
|
|
11
|
-
def __str__(self):
|
|
12
|
-
return repr(self.msg)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class InvalidDataSource(Exception):
|
|
16
|
-
def __init__(self, source):
|
|
17
|
-
self.msg = f"attempt to use and invalid data source ({source})"
|
|
18
|
-
self.status = EXIT_STATUS_GENERAL_ERROR
|
|
19
|
-
|
|
20
|
-
def __str__(self):
|
|
21
|
-
return repr(self.msg)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class
|
|
25
|
-
def __init__(self,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
self.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
1
|
+
# OSITAH exceptions not specific to one of the sub-application
|
|
2
|
+
|
|
3
|
+
from hito_tools.exceptions import EXIT_STATUS_GENERAL_ERROR
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InvalidCallbackInput(Exception):
|
|
7
|
+
def __init__(self, input_name):
|
|
8
|
+
self.msg = f"internal error: invalid input ({input_name}) in callback"
|
|
9
|
+
self.status = EXIT_STATUS_GENERAL_ERROR
|
|
10
|
+
|
|
11
|
+
def __str__(self):
|
|
12
|
+
return repr(self.msg)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InvalidDataSource(Exception):
|
|
16
|
+
def __init__(self, source):
|
|
17
|
+
self.msg = f"attempt to use and invalid data source ({source})"
|
|
18
|
+
self.status = EXIT_STATUS_GENERAL_ERROR
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return repr(self.msg)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InvalidHitoProjectName(Exception):
|
|
25
|
+
def __init__(self, projects):
|
|
26
|
+
self.msg = (
|
|
27
|
+
f"The following Hito project names don't match the format 'masterproject / project' :"
|
|
28
|
+
f" {', '.join(projects)}"
|
|
29
|
+
)
|
|
30
|
+
self.status = EXIT_STATUS_GENERAL_ERROR
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
return repr(self.msg)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SessionDataMissing(Exception):
|
|
37
|
+
def __init__(self, session_id=None):
|
|
38
|
+
if session_id:
|
|
39
|
+
session_txt = f" (session={session_id})"
|
|
40
|
+
else:
|
|
41
|
+
session_txt = ""
|
|
42
|
+
self.msg = f"Attempt to use non existing session data{session_txt}"
|
|
43
|
+
self.status = EXIT_STATUS_GENERAL_ERROR
|
|
44
|
+
|
|
45
|
+
def __str__(self):
|
|
46
|
+
return repr(self.msg)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ValidationPeriodAmbiguous(Exception):
|
|
50
|
+
def __init__(self, date):
|
|
51
|
+
self.msg = f"Configuration error: several periods matching {date}"
|
|
52
|
+
self.status = EXIT_STATUS_GENERAL_ERROR
|
|
53
|
+
|
|
54
|
+
def __str__(self):
|
|
55
|
+
return repr(self.msg)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ValidationPeriodMissing(Exception):
|
|
59
|
+
def __init__(self, date):
|
|
60
|
+
self.msg = f"No defined declaration period matching {date}"
|
|
61
|
+
self.status = EXIT_STATUS_GENERAL_ERROR
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
return repr(self.msg)
|
|
@@ -153,9 +153,11 @@ def create_progress_bar(
|
|
|
153
153
|
|
|
154
154
|
return html.Div(
|
|
155
155
|
[
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
(
|
|
157
|
+
html.Div(f"Chargement des données de l'équipe {team} en cours...")
|
|
158
|
+
if team
|
|
159
|
+
else html.Div()
|
|
160
|
+
),
|
|
159
161
|
dcc.Interval(
|
|
160
162
|
id="progress-interval",
|
|
161
163
|
max_intervals=max_intervals,
|
|
@@ -306,3 +308,32 @@ app.clientside_callback(
|
|
|
306
308
|
State({"type": TABLE_TYPE_TABLE, "id": MATCH}, "id"),
|
|
307
309
|
prevent_initial_call=True,
|
|
308
310
|
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# Emulate former Jumbotron
|
|
314
|
+
def ositah_jumbotron(title: str, main_text: str, details: str = None, title_class: str = None):
|
|
315
|
+
"""
|
|
316
|
+
Emulate Jumbotron component available in Bootstrap v4
|
|
317
|
+
|
|
318
|
+
:param title: Jumbotron title
|
|
319
|
+
:param main_text: main text
|
|
320
|
+
:param details: optional additional text
|
|
321
|
+
:return: html.div()
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
return html.Div(
|
|
325
|
+
dbc.Container(
|
|
326
|
+
[
|
|
327
|
+
html.H1(title, className=f"display-3 {title_class if ( title_class ) else ''}"),
|
|
328
|
+
html.P(
|
|
329
|
+
main_text,
|
|
330
|
+
className="lead",
|
|
331
|
+
),
|
|
332
|
+
html.Hr(className="my-2"),
|
|
333
|
+
html.P(details),
|
|
334
|
+
],
|
|
335
|
+
fluid=True,
|
|
336
|
+
className="py-3",
|
|
337
|
+
),
|
|
338
|
+
className="p-3 bg-light rounded-3",
|
|
339
|
+
)
|
|
@@ -10,7 +10,7 @@ from sqlalchemy.orm import joinedload
|
|
|
10
10
|
|
|
11
11
|
from ositah.utils.agents import get_agents
|
|
12
12
|
from ositah.utils.cache import clear_cached_data
|
|
13
|
-
from ositah.utils.exceptions import InvalidDataSource
|
|
13
|
+
from ositah.utils.exceptions import InvalidDataSource, InvalidHitoProjectName
|
|
14
14
|
from ositah.utils.hito_db import get_db
|
|
15
15
|
from ositah.utils.period import get_validation_period_data
|
|
16
16
|
from ositah.utils.utils import (
|
|
@@ -306,9 +306,9 @@ def get_team_projects(
|
|
|
306
306
|
# Drop statut column to avoid conflicts in future merge with the Agent table
|
|
307
307
|
declarations.drop(columns=["statut"], inplace=True)
|
|
308
308
|
# Ensure that email_auth is defined and if it is not, replace it by the email.
|
|
309
|
-
declarations.loc[
|
|
310
|
-
declarations[columns["
|
|
311
|
-
|
|
309
|
+
declarations.loc[declarations[columns["email_auth"]].isna(), columns["email_auth"]] = (
|
|
310
|
+
declarations[columns["email"]]
|
|
311
|
+
)
|
|
312
312
|
declarations[columns["activity"]] = declarations.apply(
|
|
313
313
|
lambda row: ositah2hito_project_name(
|
|
314
314
|
row[columns["masterproject"]], row[columns["project"]]
|
|
@@ -410,16 +410,23 @@ def get_team_projects(
|
|
|
410
410
|
if column not in saved_projects.columns:
|
|
411
411
|
saved_projects[column] = np.NaN
|
|
412
412
|
declarations = declarations.join(saved_projects)
|
|
413
|
-
declarations.loc[
|
|
414
|
-
declarations.
|
|
415
|
-
|
|
416
|
-
declarations.loc[
|
|
417
|
-
declarations.
|
|
418
|
-
|
|
413
|
+
declarations.loc[declarations.project_saved.notna(), columns["masterproject"]] = (
|
|
414
|
+
declarations.newmaster
|
|
415
|
+
)
|
|
416
|
+
declarations.loc[declarations.project_saved.notna(), columns["project"]] = (
|
|
417
|
+
declarations.newproject
|
|
418
|
+
)
|
|
419
419
|
declarations.drop(columns=["newmaster", "newproject"], inplace=True)
|
|
420
|
-
declarations.loc[
|
|
421
|
-
declarations.project_saved
|
|
422
|
-
|
|
420
|
+
declarations.loc[declarations.project_saved.notna(), columns["activity"]] = (
|
|
421
|
+
declarations.project_saved
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Detect project names not matching the format "masterproject / project"
|
|
425
|
+
invalid_hito_projects = declarations.loc[declarations[columns["project"]].isnull()]
|
|
426
|
+
if not invalid_hito_projects.empty:
|
|
427
|
+
raise InvalidHitoProjectName(
|
|
428
|
+
pd.Series(invalid_hito_projects[columns["masterproject"]]).unique()
|
|
429
|
+
)
|
|
423
430
|
|
|
424
431
|
else:
|
|
425
432
|
raise InvalidDataSource(source)
|
|
@@ -577,12 +584,12 @@ def get_hito_nsip_activities(project_activity: bool = True):
|
|
|
577
584
|
np.NaN,
|
|
578
585
|
]
|
|
579
586
|
else:
|
|
580
|
-
activities[
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
587
|
+
activities[["nsip_master", "nsip_project", "nsip_project_id", "nsip_reference_id"]] = (
|
|
588
|
+
activities.apply(
|
|
589
|
+
lambda v: nsip_activity_name_id(v["nsip_name_id"], v["class"]),
|
|
590
|
+
axis=1,
|
|
591
|
+
result_type="expand",
|
|
592
|
+
)
|
|
586
593
|
)
|
|
587
594
|
activities["nsip_project_id"] = activities["nsip_project_id"].astype(int)
|
|
588
595
|
activities["nsip_reference_id"] = activities["nsip_reference_id"].astype(int)
|
|
@@ -328,9 +328,9 @@ def define_config_params(file):
|
|
|
328
328
|
if "agent_query" not in config["hito"]["db"]:
|
|
329
329
|
raise ConfigMissingParam("hito/db/agent_query", file)
|
|
330
330
|
if config["hito"]["db"]["type"] == "sqlite":
|
|
331
|
-
app.server.config[
|
|
332
|
-
"
|
|
333
|
-
|
|
331
|
+
app.server.config["SQLALCHEMY_DATABASE_URI"] = (
|
|
332
|
+
f"sqlite:///{config['hito']['db']['location']}"
|
|
333
|
+
)
|
|
334
334
|
elif config["hito"]["db"]["type"] == "mysql":
|
|
335
335
|
if "user" not in config["hito"]["db"]:
|
|
336
336
|
raise ConfigMissingParam("hito/db/user", file)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ositah
|
|
3
|
-
Version:
|
|
3
|
+
Version: 24.2.dev1
|
|
4
4
|
Summary: Outils de Suivi d'Activités basé sur Hito
|
|
5
5
|
Author-email: Michel Jouvin <michel.jouvin@ijclab.in2p3.fr>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -12,6 +12,20 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.8
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
+
Requires-Dist: blinker
|
|
16
|
+
Requires-Dist: dash
|
|
17
|
+
Requires-Dist: dash-bootstrap-components
|
|
18
|
+
Requires-Dist: flask~=2.2
|
|
19
|
+
Requires-Dist: flask-multipass
|
|
20
|
+
Requires-Dist: flask-sqlalchemy~=3.0
|
|
21
|
+
Requires-Dist: flask-wtf
|
|
22
|
+
Requires-Dist: hito-tools>=23.2
|
|
23
|
+
Requires-Dist: pandas==2.*
|
|
24
|
+
Requires-Dist: pymysql
|
|
25
|
+
Requires-Dist: python-ldap
|
|
26
|
+
Requires-Dist: pyyaml
|
|
27
|
+
Requires-Dist: simplejson
|
|
28
|
+
Requires-Dist: sqlalchemy~=2.0
|
|
15
29
|
|
|
16
30
|
# OSITAH : Outil de Suivi de Temps et d'Activités basé sur Hito
|
|
17
31
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|