Что такое DWH
Grist
- Столбцы в Grist работают так же, как в БД: они имеют имена и содержат один тип данных. При этом они могут быть заполнены по формуле — так же, как в электронной таблице: с автоматическим обновлением при изменении ячеек, на которые имеются ссылки.
- В Grist поддерживается полный синтаксис Python, включая стандартную библиотеку. Также доступны многие функции Excel. В основе Grist SQLite. Поэтому любой инструмент, который способен читать SQLite, прочитает и данные из файла Grist.
- Здесь легко создавать резервные копии, а также перемещать документы между разными хостами.
- У Grist хорошо задокументированное API. А еще есть возможность экспортировать и импортировать Excel и Google-таблицы.
- Одна из самых интересных возможностей в Grist — настройка правил доступа для пользователей. Grist позволяет делиться документами и рабочими областями с другими пользователями, управлять доступом к отдельным строкам, столбцам и таблицам на основе значений ячеек и атрибутов пользователя. Для управления пользователями нужно настроить SSO-логин (мы используем authentik) или использовать готовое решение. Можно ознакомиться с документацией по настройке Grist в authentik.
docker pull gristlabs/grist
docker run -p 8484:8484 -v $PWD/persist:/persist -it gristlabs/grist
Почему Grist
- правила доступа для разных пользователей;
- возможность кастомизации таблиц, колонок, строк;
- быстрая проверка гипотезы.
Как мы используем Grist






user.Team.Role == "Менеджер" and user.Access != OWNER and user.Name == rec.responsible and user.Name == newRec.responsible
user.Name — имя пользователя.
user.Team.Role == "Менеджер" and user.Access != OWNER and user.Name != rec.responsible


user.Name if Table4.lookupOne(Email=user.Email).Role == "Менеджер" else value


user.Access == OWNER or user.Team.Role in ['Бухгалтер']



Grist API и Airflow

from airflow.models import Variable
from grist_api import GristDocAPI
@dag(
dag_id=dag_id,
params=params,
schedule_interval='0 * * * *',
start_date=pendulum.datetime(2022, 6, 30, 0, 0, 0, tz='UTC'),
catchup=False,
render_template_as_native_obj=True,
tags=tags,
)
def grist_T_pg():
"""
### GAD ETL: Все таблицы
Выгрузка данных из Gtist в PostgreSQL
"""
@task()
def extract(**context):
"""
#### Extract task
Получем данные из Grist по API
"""
SERVER = Variable.get('API_URL_grist')
DOC_ID = Variable.get('GRIST_DOC_ID')
api = GristDocAPI(
DOC_ID,
server=SERVER
)
data = api.call(f"tables/{context['params'].get('grist')}/data")
return data
@dag(
dag_id=dag_id,
params=params,
schedule_interval='30 1 * * *',
start_date=pendulum.datetime(2022, 6, 30, 1, 0, 0, tz='UTC'),
catchup=False,
render_template_as_native_obj=True,
tags=tags,
)
def dimension_tables():
"""
### GAD Dimension Tables
Создание dim-таблиц
"""
@task()
def read(**context):
"""
#### Read task
Чтение данных из таблицы
"""
hook = PostgresHook('dwh')
engine = hook.get_sqlalchemy_engine()
conn = engine.connect()
sql = f"SELECT * FROM {context['params'].get('dwh')}"
try:
df = pd.read_sql(
sql,
conn
)
finally:
conn.close()
return df.to_dict()
@task()
def copy(data: dict, **context):
"""
#### Copy task
Создание копии таблицы
"""
updated_today = was_update_today(table=f"{context['params'].get('dwh')}__dim")
if updated_today: return
hook = PostgresHook('dwh')
engine = hook.get_sqlalchemy_engine()
conn = engine.connect()
try:
df = pd.DataFrame.from_dict(
data=data
)
# Дата и время создания снапшота таблицы
df['ds'] = pd.to_datetime('today').normalize()
df.to_sql(
f"{context['params'].get('dwh')}__dim",
conn,
index=False,
if_exists='append'
)
finally:
conn.close()
data = read()
copy(data)
Metabase и psql-http
docker pull metabase/metabase:latest
docker run -d -p 3000:3000 --name metabase metabase/metabase
wget -O pgsqlhttp.tar.gz "https://github.com/pramsey/pgsql-http/archive/refs/tags/${PGSQL_HTTP_VERSION}.tar.gz"
mkdir -p /usr/src/pgsqlhttp
tar --extract --file pgsqlhttp.tar.gz --directory /usr/src/pgsqlhttp --strip-components 1
rm pgsqlhttp.tar.gz
cd /usr/src/pgsqlhttp
make
make install
CREATE EXTENSION IF NOT EXISTS "http";
SELECT status, content_type
FROM http_get('http://httpbin.org/');
status | content_type
--------+--------------------------
200 | text/html; charset=utf-8
(1 row) ];
}
Чтобы создать кнопку на дашборде, нужно разрешить «Действия с Моделью» в настройках подключения базы данных Metabase.



CREATE OR REPLACE FUNCTION start_dags(dags text[]) RETURNS boolean LANGUAGE PLPGSQL AS $$
DECLARE
current_dag text;
login text := 'login';
pass text := 'password';
result http_response array;
BEGIN
FOREACH current_dag IN ARRAY dags LOOP
result := array_append(result, start_dag_run(login, pass, current_dag));
END LOOP;
return true;
END;
$$;'internet' => $partner->getInternet() ? $partner->getInternet() : [],
];
}
public function getShortApiView(Partner $partner): array
{
$contacts = $this->getContacts($partner);
return [
'id' => $partner->getId(),
'name' => $partner->getName(),
'contacts' => $contacts ? $contacts : [],
'city' => ($partner->getCity()) ? $this->viewService->getApiView($partner->getCity()) : null,
'internet' => $partner->getInternet() ? $partner->getInternet() : [],
];
}
Полезная ссылка и демо-версия
Пароль: demo1234