commons-metrics 0.0.15__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.
- commons_metrics/__init__.py +9 -0
- commons_metrics/azure_devops_client.py +432 -0
- commons_metrics/commons_repos_client.py +114 -0
- commons_metrics/database.py +39 -0
- commons_metrics/github_api_client.py +305 -0
- commons_metrics/repositories.py +269 -0
- commons_metrics/update_design_components.py +203 -0
- commons_metrics/util.py +38 -0
- commons_metrics-0.0.15.dist-info/METADATA +17 -0
- commons_metrics-0.0.15.dist-info/RECORD +13 -0
- commons_metrics-0.0.15.dist-info/WHEEL +5 -0
- commons_metrics-0.0.15.dist-info/licenses/LICENSE +9 -0
- commons_metrics-0.0.15.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import base64
|
|
3
|
+
from typing import List, Dict, Optional
|
|
4
|
+
import os
|
|
5
|
+
from .commons_repos_client import extract_package_version_from_pubspec, extract_package_version_from_package_json
|
|
6
|
+
|
|
7
|
+
class GitHubAPIClient:
|
|
8
|
+
"""
|
|
9
|
+
Cliente para interactuar con la API de GitHub
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, token: str, owner: str = None, repo: str = None):
|
|
13
|
+
"""
|
|
14
|
+
Inicializa el cliente de GitHub API
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
token: Personal Access Token de GitHub
|
|
18
|
+
owner: Dueño del repositorio (ej: 'grupobancolombia-innersource') - Opcional
|
|
19
|
+
repo: Nombre del repositorio (ej: 'NU0066001_BDS_MOBILE_Lib') - Opcional
|
|
20
|
+
"""
|
|
21
|
+
self.token = token
|
|
22
|
+
self.owner = owner
|
|
23
|
+
self.repo = repo
|
|
24
|
+
self.base_url = f"https://api.github.com/repos/{owner}/{repo}" if owner and repo else "https://api.github.com"
|
|
25
|
+
self.headers = {
|
|
26
|
+
"Authorization": f"token {token}",
|
|
27
|
+
"Accept": "application/vnd.github.v3+json"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def get_directory_contents(self, path: str = "") -> List[Dict]:
|
|
31
|
+
"""
|
|
32
|
+
Obtiene el contenido de un directorio en el repositorio
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
path: Ruta del directorio (ej: 'lib/atoms')
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Lista de diccionarios con información de archivos/carpetas
|
|
39
|
+
"""
|
|
40
|
+
url = f"{self.base_url}/contents/{path}"
|
|
41
|
+
response = requests.get(url, headers=self.headers)
|
|
42
|
+
|
|
43
|
+
if response.status_code == 200:
|
|
44
|
+
return response.json()
|
|
45
|
+
elif response.status_code == 404:
|
|
46
|
+
return []
|
|
47
|
+
else:
|
|
48
|
+
raise Exception(f"Error getting directory contents: {response.status_code} - {response.text}")
|
|
49
|
+
|
|
50
|
+
def get_file_content(self, path: str) -> Optional[str]:
|
|
51
|
+
"""
|
|
52
|
+
Obtiene el contenido de un archivo
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
path: Ruta del archivo en el repositorio
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Contenido del archivo como string, o None si no existe
|
|
59
|
+
"""
|
|
60
|
+
url = f"{self.base_url}/contents/{path}"
|
|
61
|
+
response = requests.get(url, headers=self.headers)
|
|
62
|
+
|
|
63
|
+
if response.status_code == 200:
|
|
64
|
+
content = response.json()
|
|
65
|
+
if content.get('encoding') == 'base64':
|
|
66
|
+
decoded_content = base64.b64decode(content['content']).decode('utf-8')
|
|
67
|
+
return decoded_content
|
|
68
|
+
return None
|
|
69
|
+
elif response.status_code == 404:
|
|
70
|
+
return None
|
|
71
|
+
else:
|
|
72
|
+
raise Exception(f"Error getting file content: {response.status_code} - {response.text}")
|
|
73
|
+
|
|
74
|
+
def list_folders_in_directory(self, path: str) -> List[str]:
|
|
75
|
+
"""
|
|
76
|
+
Lista solo las carpetas dentro de un directorio
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
path: Ruta del directorio
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Lista de nombres de carpetas
|
|
83
|
+
"""
|
|
84
|
+
contents = self.get_directory_contents(path)
|
|
85
|
+
folders = [
|
|
86
|
+
item['name']
|
|
87
|
+
for item in contents
|
|
88
|
+
if item['type'] == 'dir'
|
|
89
|
+
]
|
|
90
|
+
return folders
|
|
91
|
+
|
|
92
|
+
def walk_directory(self, path: str = "", extension: str = None, exclude_patterns: List[str] = None) -> List[Dict]:
|
|
93
|
+
"""
|
|
94
|
+
Recorre recursivamente un directorio y retorna todos los archivos
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
path: Ruta del directorio inicial
|
|
98
|
+
extension: Extensión a filtrar (ej: '.ts', '.dart')
|
|
99
|
+
exclude_patterns: Lista de patrones a excluir (ej: ['.spec.', '.test.', '.d.ts'])
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Lista de diccionarios con información de archivos encontrados
|
|
103
|
+
"""
|
|
104
|
+
all_files = []
|
|
105
|
+
exclude_patterns = exclude_patterns or []
|
|
106
|
+
|
|
107
|
+
def should_exclude(filename: str) -> bool:
|
|
108
|
+
return any(pattern in filename for pattern in exclude_patterns)
|
|
109
|
+
|
|
110
|
+
def recurse_directory(current_path: str):
|
|
111
|
+
contents = self.get_directory_contents(current_path)
|
|
112
|
+
|
|
113
|
+
for item in contents:
|
|
114
|
+
item_path = f"{current_path}/{item['name']}" if current_path else item['name']
|
|
115
|
+
|
|
116
|
+
if item['type'] == 'file':
|
|
117
|
+
# Aplicar filtros de extensión y exclusión
|
|
118
|
+
if extension and not item['name'].endswith(extension):
|
|
119
|
+
continue
|
|
120
|
+
if should_exclude(item['name']):
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
all_files.append({
|
|
124
|
+
'name': item['name'],
|
|
125
|
+
'path': item_path,
|
|
126
|
+
'url': item.get('url', ''),
|
|
127
|
+
'download_url': item.get('download_url', '')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
elif item['type'] == 'dir':
|
|
131
|
+
# Excluir directorios comunes que no contienen componentes
|
|
132
|
+
if item['name'] not in ['node_modules', 'dist', 'build', '.git', 'test', 'tests', '__pycache__']:
|
|
133
|
+
recurse_directory(item_path)
|
|
134
|
+
|
|
135
|
+
recurse_directory(path)
|
|
136
|
+
return all_files
|
|
137
|
+
|
|
138
|
+
def search_code(self, query: str, per_page: int = 100) -> List[Dict]:
|
|
139
|
+
"""
|
|
140
|
+
Busca código en GitHub usando la API de búsqueda
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
query: Query de búsqueda (ej: '"bds_mobile" in:file filename:pubspec.yaml')
|
|
144
|
+
per_page: Número de resultados por página (máximo 100)
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Lista de diccionarios con información de archivos encontrados
|
|
148
|
+
"""
|
|
149
|
+
all_results = []
|
|
150
|
+
page = 1
|
|
151
|
+
|
|
152
|
+
while True:
|
|
153
|
+
url = "https://api.github.com/search/code"
|
|
154
|
+
params = {
|
|
155
|
+
'q': query,
|
|
156
|
+
'per_page': per_page,
|
|
157
|
+
'page': page
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
response = requests.get(url, headers=self.headers, params=params)
|
|
161
|
+
|
|
162
|
+
if response.status_code == 200:
|
|
163
|
+
data = response.json()
|
|
164
|
+
items = data.get('items', [])
|
|
165
|
+
|
|
166
|
+
if not items:
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
all_results.extend(items)
|
|
170
|
+
|
|
171
|
+
# Si hay menos items que per_page, es la última página
|
|
172
|
+
if len(items) < per_page:
|
|
173
|
+
break
|
|
174
|
+
|
|
175
|
+
page += 1
|
|
176
|
+
elif response.status_code == 403:
|
|
177
|
+
# Rate limit alcanzado
|
|
178
|
+
print(f"⚠️ Rate limit alcanzado. Resultados obtenidos: {len(all_results)}")
|
|
179
|
+
break
|
|
180
|
+
else:
|
|
181
|
+
raise Exception(f"Error searching code: {response.status_code} - {response.text}")
|
|
182
|
+
|
|
183
|
+
return all_results
|
|
184
|
+
|
|
185
|
+
def search_projects_with_bds(self, platform: str, design_system_name: str = None) -> List[Dict]:
|
|
186
|
+
"""
|
|
187
|
+
Busca proyectos que usan el sistema de diseño BDS (mobile o web)
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
platform: 'mobile' o 'web'
|
|
191
|
+
design_system_name: Nombre del paquete del sistema de diseño
|
|
192
|
+
Si no se proporciona, usa valores por defecto:
|
|
193
|
+
- mobile: "bds_mobile"
|
|
194
|
+
- web: "@bancolombia/design-system-web"
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Lista de proyectos con información del repositorio incluyendo la versión
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
ValueError: Si platform no es 'mobile' o 'web'
|
|
201
|
+
"""
|
|
202
|
+
# Validar plataforma
|
|
203
|
+
if platform not in ['mobile', 'web']:
|
|
204
|
+
raise ValueError(f"Platform debe ser 'mobile' o 'web', se recibió: {platform}")
|
|
205
|
+
|
|
206
|
+
# Configurar valores según la plataforma
|
|
207
|
+
if platform == 'mobile':
|
|
208
|
+
config_file = 'pubspec.yaml'
|
|
209
|
+
default_package_name = 'bds_mobile'
|
|
210
|
+
extract_version_method = extract_package_version_from_pubspec
|
|
211
|
+
else: # web
|
|
212
|
+
config_file = 'package.json'
|
|
213
|
+
default_package_name = '@bancolombia/design-system-web'
|
|
214
|
+
extract_version_method = extract_package_version_from_package_json
|
|
215
|
+
|
|
216
|
+
# Usar nombre de paquete por defecto si no se proporciona
|
|
217
|
+
package_name = design_system_name or default_package_name
|
|
218
|
+
|
|
219
|
+
# Agregar filtro de organización si self.owner está definido
|
|
220
|
+
org_filter = f" org:{self.owner}" if self.owner else ""
|
|
221
|
+
query = f'"{package_name}" in:file filename:{config_file}{org_filter}'
|
|
222
|
+
results = self.search_code(query)
|
|
223
|
+
|
|
224
|
+
projects = []
|
|
225
|
+
for item in results:
|
|
226
|
+
# Obtener contenido del archivo de configuración para extraer la versión
|
|
227
|
+
file_content = self._get_file_content_from_url(item['url'])
|
|
228
|
+
version = extract_version_method(file_content, package_name)
|
|
229
|
+
|
|
230
|
+
project_info = {
|
|
231
|
+
'name': item['repository']['name'],
|
|
232
|
+
'full_name': item['repository']['full_name'],
|
|
233
|
+
'owner': item['repository']['owner']['login'],
|
|
234
|
+
'repo_url': item['repository']['html_url'],
|
|
235
|
+
'file_path': item['path'],
|
|
236
|
+
'bds_version': version
|
|
237
|
+
}
|
|
238
|
+
projects.append(project_info)
|
|
239
|
+
|
|
240
|
+
return projects
|
|
241
|
+
|
|
242
|
+
def search_repositories(self, query: str, per_page: int = 100) -> List[Dict]:
|
|
243
|
+
"""
|
|
244
|
+
Busca repositorios en GitHub usando la API de búsqueda
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
query: Query de búsqueda (ej: 'NU0296001 mobile in:name org:grupobancolombia-innersource')
|
|
248
|
+
per_page: Número de resultados por página (máximo 100)
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Lista de diccionarios con información de repositorios encontrados
|
|
252
|
+
"""
|
|
253
|
+
all_results = []
|
|
254
|
+
page = 1
|
|
255
|
+
search_url = "https://api.github.com/search/repositories"
|
|
256
|
+
|
|
257
|
+
while True:
|
|
258
|
+
params = {
|
|
259
|
+
'q': query,
|
|
260
|
+
'per_page': per_page,
|
|
261
|
+
'page': page
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
response = requests.get(search_url, headers=self.headers, params=params)
|
|
265
|
+
|
|
266
|
+
if response.status_code != 200:
|
|
267
|
+
print(f"Error searching repositories: {response.status_code}")
|
|
268
|
+
print(f"Response: {response.text}")
|
|
269
|
+
break
|
|
270
|
+
|
|
271
|
+
data = response.json()
|
|
272
|
+
items = data.get('items', [])
|
|
273
|
+
|
|
274
|
+
if not items:
|
|
275
|
+
break
|
|
276
|
+
|
|
277
|
+
all_results.extend(items)
|
|
278
|
+
|
|
279
|
+
# Verificar si hay más páginas
|
|
280
|
+
if len(items) < per_page:
|
|
281
|
+
break
|
|
282
|
+
|
|
283
|
+
page += 1
|
|
284
|
+
|
|
285
|
+
return all_results
|
|
286
|
+
|
|
287
|
+
def _get_file_content_from_url(self, api_url: str) -> Optional[str]:
|
|
288
|
+
"""
|
|
289
|
+
Obtiene el contenido de un archivo desde una URL de la API de GitHub
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
api_url: URL de la API de GitHub para el archivo
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Contenido del archivo como string, o None si no existe
|
|
296
|
+
"""
|
|
297
|
+
response = requests.get(api_url, headers=self.headers)
|
|
298
|
+
|
|
299
|
+
if response.status_code == 200:
|
|
300
|
+
content = response.json()
|
|
301
|
+
if content.get('encoding') == 'base64':
|
|
302
|
+
decoded_content = base64.b64decode(content['content']).decode('utf-8')
|
|
303
|
+
return decoded_content
|
|
304
|
+
return None
|
|
305
|
+
return None
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
class ComponentRepository:
|
|
4
|
+
"""
|
|
5
|
+
Repository class for component-specific database operations
|
|
6
|
+
"""
|
|
7
|
+
def __init__(self, db_connection):
|
|
8
|
+
self.db = db_connection
|
|
9
|
+
|
|
10
|
+
def save_component(self, technical_name, functional_name, id_type, folder, class_name, status):
|
|
11
|
+
cursor = None
|
|
12
|
+
try:
|
|
13
|
+
cursor = self.db.connection.cursor()
|
|
14
|
+
|
|
15
|
+
insert_query = """
|
|
16
|
+
INSERT INTO schmetrc.component(technical_name,functional_name, type_id, folder, class_name, status)
|
|
17
|
+
VALUES (%s, %s, %s, %s, %s, %s)
|
|
18
|
+
RETURNING id;
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
cursor.execute(insert_query, (technical_name, functional_name, id_type, folder, class_name, status))
|
|
22
|
+
component_id = cursor.fetchone()[0]
|
|
23
|
+
self.db.commit_transaction()
|
|
24
|
+
|
|
25
|
+
return component_id
|
|
26
|
+
|
|
27
|
+
except Exception as e:
|
|
28
|
+
self.db.rollback_transaction()
|
|
29
|
+
raise Exception(f"Error saving component '{technical_name}': {str(e)}")
|
|
30
|
+
finally:
|
|
31
|
+
if cursor:
|
|
32
|
+
cursor.close()
|
|
33
|
+
|
|
34
|
+
def query_components_by_type(self, id_type: int) -> list:
|
|
35
|
+
"""
|
|
36
|
+
Consulta componentes de un tipo específico en la base de datos
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
id_type (int): ID del tipo de componente a consultar
|
|
40
|
+
"""
|
|
41
|
+
cursor = None
|
|
42
|
+
try:
|
|
43
|
+
cursor = self.db.connection.cursor()
|
|
44
|
+
|
|
45
|
+
query = """
|
|
46
|
+
SELECT id, technical_name, type_id, status, functional_name, folder, class_name
|
|
47
|
+
FROM schmetrc.component
|
|
48
|
+
WHERE type_id = %s
|
|
49
|
+
ORDER BY technical_name
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
cursor.execute(query, (id_type,))
|
|
53
|
+
results = cursor.fetchall()
|
|
54
|
+
|
|
55
|
+
components = []
|
|
56
|
+
for row in results:
|
|
57
|
+
component = {
|
|
58
|
+
'id': row[0],
|
|
59
|
+
'technical_name': row[1],
|
|
60
|
+
'type_id': row[2],
|
|
61
|
+
'status': row[3],
|
|
62
|
+
'functional_name': row[4],
|
|
63
|
+
'folder': row[5],
|
|
64
|
+
'class_name': row[6]
|
|
65
|
+
}
|
|
66
|
+
components.append(component)
|
|
67
|
+
|
|
68
|
+
return components
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
raise Exception(f"Error querying components by type {id_type}: {str(e)}")
|
|
72
|
+
finally:
|
|
73
|
+
if cursor:
|
|
74
|
+
cursor.close()
|
|
75
|
+
|
|
76
|
+
def query_versions_by_type(self, type_id: int) -> list:
|
|
77
|
+
"""
|
|
78
|
+
Consulta versiones de un tipo específico (mobile o web) en la base de datos
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
type_id (int): ID del tipo (13 para mobile, 12 para web)
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
list: Lista de versiones con sus IDs
|
|
85
|
+
"""
|
|
86
|
+
cursor = None
|
|
87
|
+
try:
|
|
88
|
+
cursor = self.db.connection.cursor()
|
|
89
|
+
query = """
|
|
90
|
+
SELECT id, version, type_id, creation_date
|
|
91
|
+
FROM schmetrc.general_version
|
|
92
|
+
WHERE type_id = %s
|
|
93
|
+
ORDER BY version DESC
|
|
94
|
+
"""
|
|
95
|
+
cursor.execute(query, (type_id,))
|
|
96
|
+
results = cursor.fetchall()
|
|
97
|
+
|
|
98
|
+
# Get column names from cursor description
|
|
99
|
+
column_names = [desc[0] for desc in cursor.description]
|
|
100
|
+
|
|
101
|
+
versions = []
|
|
102
|
+
for row in results:
|
|
103
|
+
# Crear versión dinámicamente usando los nombres de columnas
|
|
104
|
+
version = dict(zip(column_names, row))
|
|
105
|
+
versions.append(version)
|
|
106
|
+
|
|
107
|
+
return versions
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
raise RuntimeError(f"Error querying versions by type {type_id}: {str(e)}")
|
|
111
|
+
finally:
|
|
112
|
+
if cursor:
|
|
113
|
+
cursor.close()
|
|
114
|
+
|
|
115
|
+
def save_adoption(self, version_id: int, component_id: int, aplication_code: str, count: int, historical_metric_id: int = None):
|
|
116
|
+
"""
|
|
117
|
+
Inserta un registro de adopción en la tabla schmetrc.general_adoption
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
version_id (int): ID de la versión del sistema de diseño
|
|
121
|
+
component_id (int): ID del componente
|
|
122
|
+
aplication_code (str): Código de aplicación (ej: NU0296001)
|
|
123
|
+
count (int): Cantidad de usos del componente
|
|
124
|
+
historical_metric_id (int, optional): ID de la fecha en measurement_systems_history. Por defecto None
|
|
125
|
+
"""
|
|
126
|
+
cursor = None
|
|
127
|
+
try:
|
|
128
|
+
cursor = self.db.connection.cursor()
|
|
129
|
+
insert_query = """
|
|
130
|
+
INSERT INTO schmetrc.general_adoption(version_id, component_id, aplication_code, count, historical_metric_id)
|
|
131
|
+
VALUES (%s, %s, %s, %s, %s)
|
|
132
|
+
RETURNING id;
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
cursor.execute(insert_query, (version_id, component_id, aplication_code, count, historical_metric_id))
|
|
136
|
+
adoption_id = cursor.fetchone()[0]
|
|
137
|
+
self.db.commit_transaction()
|
|
138
|
+
return adoption_id
|
|
139
|
+
except Exception as e:
|
|
140
|
+
self.db.rollback_transaction()
|
|
141
|
+
raise RuntimeError(f"Error saving adoption for component {component_id}: {str(e)}")
|
|
142
|
+
finally:
|
|
143
|
+
if cursor:
|
|
144
|
+
cursor.close()
|
|
145
|
+
|
|
146
|
+
def get_or_create_current_month_date(self) -> int:
|
|
147
|
+
"""
|
|
148
|
+
Obtiene el ID de la fecha del mes actual en measurement_systems_history.
|
|
149
|
+
Si no existe, crea un registro con la fecha de hoy.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
int: ID del registro de fecha
|
|
153
|
+
"""
|
|
154
|
+
cursor = None
|
|
155
|
+
try:
|
|
156
|
+
cursor = self.db.connection.cursor()
|
|
157
|
+
|
|
158
|
+
# Buscar si existe un registro del mes actual
|
|
159
|
+
query_select = """
|
|
160
|
+
SELECT id
|
|
161
|
+
FROM schmetrc.measurement_systems_history
|
|
162
|
+
WHERE EXTRACT(YEAR FROM date) = EXTRACT(YEAR FROM CURRENT_DATE)
|
|
163
|
+
AND EXTRACT(MONTH FROM date) = EXTRACT(MONTH FROM CURRENT_DATE)
|
|
164
|
+
LIMIT 1
|
|
165
|
+
"""
|
|
166
|
+
cursor.execute(query_select)
|
|
167
|
+
result = cursor.fetchone()
|
|
168
|
+
|
|
169
|
+
if result:
|
|
170
|
+
# Ya existe un registro del mes actual
|
|
171
|
+
date_id = result[0]
|
|
172
|
+
print(f" ℹ️ Usando fecha existente del mes actual (ID: {date_id})")
|
|
173
|
+
return date_id
|
|
174
|
+
else:
|
|
175
|
+
# No existe, crear uno nuevo con la fecha de hoy
|
|
176
|
+
query_insert = """
|
|
177
|
+
INSERT INTO schmetrc.measurement_systems_history (date)
|
|
178
|
+
VALUES (CURRENT_DATE)
|
|
179
|
+
RETURNING id
|
|
180
|
+
"""
|
|
181
|
+
cursor.execute(query_insert)
|
|
182
|
+
date_id = cursor.fetchone()[0]
|
|
183
|
+
self.db.commit_transaction()
|
|
184
|
+
print(f" ✅ Creado nuevo registro de fecha (ID: {date_id})")
|
|
185
|
+
return date_id
|
|
186
|
+
|
|
187
|
+
except Exception as e:
|
|
188
|
+
self.db.rollback_transaction()
|
|
189
|
+
raise RuntimeError(f"Error getting or creating current month date: {str(e)}")
|
|
190
|
+
finally:
|
|
191
|
+
if cursor:
|
|
192
|
+
cursor.close()
|
|
193
|
+
|
|
194
|
+
def delete_adoption_by_date_id(self, date_id: int, type_id: int = None) -> int:
|
|
195
|
+
"""
|
|
196
|
+
Elimina todos los registros de adopción asociados a un historical_metric_id específico.
|
|
197
|
+
Opcionalmente filtra por tipo de componente (mobile=13, web=12).
|
|
198
|
+
Útil para limpiar datos del mes actual antes de una nueva inserción.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
date_id (int): ID de la fecha cuyos registros se eliminarán (historical_metric_id)
|
|
202
|
+
type_id (int, optional): ID del tipo de componente para filtrar. Si es None, elimina todos los tipos.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
int: Cantidad de registros eliminados
|
|
206
|
+
"""
|
|
207
|
+
cursor = None
|
|
208
|
+
try:
|
|
209
|
+
cursor = self.db.connection.cursor()
|
|
210
|
+
|
|
211
|
+
# Construir query con o sin filtro de tipo
|
|
212
|
+
if type_id is not None:
|
|
213
|
+
# Contar registros a eliminar (con filtro de tipo)
|
|
214
|
+
count_query = """
|
|
215
|
+
SELECT COUNT(*)
|
|
216
|
+
FROM schmetrc.general_adoption ga
|
|
217
|
+
INNER JOIN schmetrc.component c ON ga.component_id = c.id
|
|
218
|
+
WHERE ga.historical_metric_id = %s AND c.type_id = %s
|
|
219
|
+
"""
|
|
220
|
+
cursor.execute(count_query, (date_id, type_id))
|
|
221
|
+
count = cursor.fetchone()[0]
|
|
222
|
+
|
|
223
|
+
if count > 0:
|
|
224
|
+
# Eliminar los registros (con filtro de tipo)
|
|
225
|
+
delete_query = """
|
|
226
|
+
DELETE FROM schmetrc.general_adoption
|
|
227
|
+
WHERE id IN (
|
|
228
|
+
SELECT ga.id
|
|
229
|
+
FROM schmetrc.general_adoption ga
|
|
230
|
+
INNER JOIN schmetrc.component c ON ga.component_id = c.id
|
|
231
|
+
WHERE ga.historical_metric_id = %s AND c.type_id = %s
|
|
232
|
+
)
|
|
233
|
+
"""
|
|
234
|
+
cursor.execute(delete_query, (date_id, type_id))
|
|
235
|
+
self.db.commit_transaction()
|
|
236
|
+
type_name = "mobile" if type_id == 13 else "web" if type_id == 12 else f"tipo {type_id}"
|
|
237
|
+
print(f" 🗑️ Eliminados {count} registros de adopción {type_name} del mes actual")
|
|
238
|
+
else:
|
|
239
|
+
print(" ℹ️ No hay registros previos de adopción para eliminar")
|
|
240
|
+
else:
|
|
241
|
+
# Contar registros a eliminar (sin filtro de tipo)
|
|
242
|
+
count_query = """
|
|
243
|
+
SELECT COUNT(*)
|
|
244
|
+
FROM schmetrc.general_adoption
|
|
245
|
+
WHERE historical_metric_id = %s
|
|
246
|
+
"""
|
|
247
|
+
cursor.execute(count_query, (date_id,))
|
|
248
|
+
count = cursor.fetchone()[0]
|
|
249
|
+
|
|
250
|
+
if count > 0:
|
|
251
|
+
# Eliminar los registros (sin filtro de tipo)
|
|
252
|
+
delete_query = """
|
|
253
|
+
DELETE FROM schmetrc.general_adoption
|
|
254
|
+
WHERE historical_metric_id = %s
|
|
255
|
+
"""
|
|
256
|
+
cursor.execute(delete_query, (date_id,))
|
|
257
|
+
self.db.commit_transaction()
|
|
258
|
+
print(f" 🗑️ Eliminados {count} registros de adopción del mes actual")
|
|
259
|
+
else:
|
|
260
|
+
print(" ℹ️ No hay registros previos de adopción para eliminar")
|
|
261
|
+
|
|
262
|
+
return count
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
self.db.rollback_transaction()
|
|
266
|
+
raise RuntimeError(f"Error deleting adoption records for date_id {date_id}: {str(e)}")
|
|
267
|
+
finally:
|
|
268
|
+
if cursor:
|
|
269
|
+
cursor.close()
|