Compare commits

..

20 Commits

Author SHA1 Message Date
013c9c5a15 fix prod v1
All checks were successful
Deploy to Production / deploy (push) Successful in 3s
2025-07-30 14:25:48 +03:00
b3e1a5c88c Merge branch 'main' into prod
Some checks failed
Deploy to Production / deploy (push) Failing after 1s
2025-07-30 14:12:05 +03:00
8e757afe39 fix testing v3
All checks were successful
Test Build / test-build (push) Successful in 3s
2025-07-29 19:52:18 +03:00
5100c5d17c v1
All checks were successful
Test Build / test-build (push) Successful in 3s
2025-07-29 19:43:11 +03:00
81d33bebef test fix v1 2025-07-29 19:42:55 +03:00
f7ed20de0e Merge branch 'main' into prod
Some checks failed
Deploy to Production / deploy (push) Failing after 3s
v1.0.0 release
2025-07-26 21:39:56 +03:00
3500d433ea fix reqs
All checks were successful
Test Build / test-build (push) Successful in 21s
2025-07-26 06:00:54 +03:00
c713b47d58 reqs fix 2025-07-26 06:00:09 +03:00
ddd0ffbcb0 vv
All checks were successful
Test Build / test-build (push) Successful in 3s
2025-07-26 05:56:11 +03:00
36a8548562 multiolap fixed 2025-07-26 05:53:45 +03:00
995b539a67 ver 14.1
Some checks failed
Deploy to Production / deploy (push) Failing after 2s
2025-07-25 04:33:15 +03:00
2515294c56 ver 14
Some checks failed
Deploy to Production / deploy (push) Failing after 2s
2025-07-25 04:32:34 +03:00
88d43124f7 v7
All checks were successful
Deploy to Production / deploy (push) Successful in 29s
2025-07-25 03:47:26 +03:00
56db68768b v6
Some checks failed
Deploy to Production / deploy (push) Failing after 3s
2025-07-25 03:44:02 +03:00
b9e248c02e 11
Some checks failed
Deploy to Production / deploy (push) Failing after 1s
2025-07-25 03:26:30 +03:00
a73c714d88 fix d v5
Some checks failed
Deploy to Production / deploy (push) Failing after 1s
2025-07-25 03:24:02 +03:00
25e53aa84e fix deployment v4
Some checks failed
Deploy to Production / deploy (push) Failing after 1s
2025-07-25 03:20:37 +03:00
ae0290145a fix deployment v3
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
2025-07-25 03:17:52 +03:00
d21b3b3214 fix deployment v2
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
2025-07-25 03:16:25 +03:00
008b2e57f2 test deploy
Some checks failed
Deploy to Production / deploy (push) Failing after 9s
2025-07-25 03:08:24 +03:00
7 changed files with 526 additions and 428 deletions

View File

@@ -7,19 +7,41 @@ on:
jobs: jobs:
deploy: deploy:
runs-on: [docker:host] runs-on: [docker, host]
steps: steps:
- name: Checkout code - name: Prepare workspace
uses: actions/checkout@v3 run: |
rm -rf /tmp/olaper || true
mkdir -p /tmp/olaper
cd /tmp/olaper
apk update && apk add openssh docker-cli
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -p 2222 10.25.100.250 >> ~/.ssh/known_hosts
git clone --branch prod ssh://git@10.25.100.250:2222/serty/olaper.git .
- name: Build Docker image - name: Build Docker image
run: | run: |
cd /tmp/olaper
docker build -t olaper:latest . docker build -t olaper:latest .
- name: Stop old container (if running) - name: Create volume (if not exists)
run: | run: |
if [ "$(docker ps -q -f name=olaper)" ]; then docker volume create olaper_data || true
docker stop olaper && docker rm olaper
- name: Stop and remove old containers
run: |
# Stop and remove container named olaper if exists
docker stop olaper || true
docker rm olaper || true
# Stop and remove any container using port 5005
PORT=5005
CONTAINER_IDS=$(docker ps -q --filter "publish=$PORT")
if [ -n "$CONTAINER_IDS" ]; then
echo "Stopping containers using port $PORT..."
docker stop $CONTAINER_IDS
docker rm $CONTAINER_IDS
fi fi
- name: Run new container - name: Run new container
@@ -30,4 +52,8 @@ jobs:
-p 5005:5005 \ -p 5005:5005 \
-e SECRET_KEY=${{ secrets.SECRET_KEY }} \ -e SECRET_KEY=${{ secrets.SECRET_KEY }} \
-e ENCRYPTION_KEY=${{ secrets.ENCRYPTION_KEY }} \ -e ENCRYPTION_KEY=${{ secrets.ENCRYPTION_KEY }} \
-v olaper_data:/opt/olaper/data \
olaper:latest olaper:latest
- name: Cleanup
run: rm -rf /tmp/olaper || true

View File

@@ -39,6 +39,7 @@ jobs:
docker run -d \ docker run -d \
--name olaper_test \ --name olaper_test \
-p 5050:5005 \ -p 5050:5005 \
-v /home/master/olaper-debug/data:/opt/olaper/data \
-e SECRET_KEY=${{ secrets.SECRET_KEY }} \ -e SECRET_KEY=${{ secrets.SECRET_KEY }} \
-e ENCRYPTION_KEY=${{ secrets.ENCRYPTION_KEY }} \ -e ENCRYPTION_KEY=${{ secrets.ENCRYPTION_KEY }} \
olaper:test olaper:test

Binary file not shown.

View File

@@ -328,32 +328,103 @@ def render_olap():
if req_module.login(): if req_module.login():
result = req_module.take_olap(json_body) result = req_module.take_olap(json_body)
# --- НАЧАЛО НОВОЙ УЛУЧШЕННОЙ ЛОГИКИ ОБРАБОТКИ ДАННЫХ ---
if 'data' not in result or not isinstance(result['data'], list): if 'data' not in result or not isinstance(result['data'], list):
flash(_('Error: Unexpected response format from RMS for report "%(name)s".', name=preset.get('name', report_id)), 'error') flash(_('Error: Unexpected response format from RMS for report "%(name)s".', name=preset.get('name', report_id)), 'error')
current_app.logger.error(f"Unexpected API response for report {report_id} ('{preset.get('name')}'). Response: {result}")
return redirect(url_for('.index')) return redirect(url_for('.index'))
data_to_insert = [] report_data = result['data']
if result['data']:
headers = list(result['data'][0].keys()) # Если отчет пуст, очищаем лист и уведомляем пользователя.
data_to_insert.append(headers) if not report_data:
for item in result['data']: gs_client.clear_and_write_data(sheet_title, [])
data_to_insert.append([item.get(h) for h in headers]) flash(_('Report "%(name)s" returned no data for the selected period. Sheet "%(sheet)s" has been cleared.', name=preset.get('name', report_id), sheet=sheet_title), 'warning')
return redirect(url_for('.index'))
# Здесь будет храниться наш итоговый "плоский" список словарей
processed_data = []
# Проверяем структуру отчета: сводный (pivoted) или простой (flat)
first_item = report_data[0]
is_pivoted = isinstance(first_item, dict) and 'row' in first_item and 'cells' in first_item
if is_pivoted:
current_app.logger.info(f"Processing a pivoted report: {preset.get('name', report_id)}")
# "Разворачиваем" (unpivot) данные в плоский список словарей
for row_item in report_data:
row_values = row_item.get('row', {})
cells = row_item.get('cells', [])
if not cells:
# Обрабатываем строки, у которых может не быть данных в ячейках
processed_data.append(row_values.copy())
else:
for cell in cells:
new_flat_row = row_values.copy()
new_flat_row.update(cell.get('col', {}))
new_flat_row.update(cell.get('values', {}))
processed_data.append(new_flat_row)
else:
current_app.logger.info(f"Processing a simple flat report: {preset.get('name', report_id)}")
# Данные уже в виде плоского списка, просто присваиваем
processed_data = [item for item in report_data if isinstance(item, dict)]
# --- Универсальное формирование заголовков и данных ---
# 1. Собираем все уникальные ключи из всех строк для гарантии целостности.
all_keys = set()
for row in processed_data:
all_keys.update(row.keys())
# 2. Создаем упорядоченный список заголовков для лучшей читаемости.
# Используем поля из пресета для определения логического порядка.
row_group_fields = preset.get('groupByRowFields', [])
col_group_fields = preset.get('groupByColFields', [])
agg_fields = preset.get('aggregateFields', [])
ordered_headers = []
# Сначала добавляем известные поля из пресета в логической последовательности.
for field in row_group_fields + col_group_fields + agg_fields:
if field in all_keys:
ordered_headers.append(field)
all_keys.remove(field)
# Добавляем любые другие (неожиданные) поля, отсортировав их по алфавиту.
ordered_headers.extend(sorted(list(all_keys)))
# 3. Собираем итоговый список списков для Google Sheets, приводя все значения к строкам.
data_to_insert = [ordered_headers]
for row in processed_data:
row_data = []
for header in ordered_headers:
value_str = str(row.get(header, ''))
if value_str.startswith(('=', '+', '-', '@')):
row_data.append("'" + value_str)
else:
row_data.append(value_str)
# Преобразуем None в пустую строку, а все остальное в строковое представление.
# Это предотвращает потенциальные ошибки типов со стороны Google Sheets API.
data_to_insert.append(row_data)
gs_client.clear_and_write_data(sheet_title, data_to_insert) gs_client.clear_and_write_data(sheet_title, data_to_insert)
if len(data_to_insert) > 1: rows_count = len(data_to_insert) - 1
flash(_('Report "%(name)s" data successfully written to sheet "%(sheet)s".', name=preset.get('name', report_id), sheet=sheet_title), 'success') flash(_('Report "%(name)s" data (%(rows)s rows) successfully written to sheet "%(sheet)s".',
else: name=preset.get('name', report_id),
flash(_('Report "%(name)s" returned no data for the selected period. Sheet "%(sheet)s" has been cleared.', name=preset.get('name', report_id), sheet=sheet_title), 'warning') rows=rows_count,
sheet=sheet_title), 'success')
else: else:
flash(_('Error authorizing on RMS server when trying to get a report.'), 'error') flash(_('Error authorizing on RMS server when trying to get a report.'), 'error')
except ValueError as ve: except ValueError as ve:
flash(_('Data Error: %(error)s', error=str(ve)), 'error') flash(_('Data Error: %(error)s', error=str(ve)), 'error')
except gspread.exceptions.APIError as api_err: except gspread.exceptions.APIError as api_err:
flash(_('Google API Error accessing sheet "%(sheet)s". Check service account permissions.', sheet=sheet_title), 'error') flash(_('Google API Error accessing sheet "%(sheet)s". Check service account permissions.', sheet=sheet_title or _('Unknown')), 'error')
current_app.logger.error(f"Google API Error for sheet '{sheet_title}': {api_err}", exc_info=True)
except Exception as e: except Exception as e:
flash(_('An unexpected error occurred: %(error)s', error=str(e)), 'error') flash(_('An unexpected error occurred: %(error)s', error=str(e)), 'error')
current_app.logger.error(f"Unexpected error in render_olap: {e}", exc_info=True)
finally: finally:
if req_module and req_module.token: if req_module and req_module.token:
req_module.logout() req_module.logout()