Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some facets #5

Merged
merged 1 commit into from
Dec 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions redash-search-sync/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ version = "0.1.0"
[dependencies]
anyhow = {version = "1.0.58", features = ["backtrace"]}
async-trait = "0.1.60"
chrono = { version = "0.4.23", features = ["serde"] }
config = "0.13.1"
hyper = {version = "0.14", features = ["full"]}
once_cell = "1.17.0"
opensearch = "2.0.0"
reqwest = {version = "0.11", features = ["json"]}
serde = {version = "1.0.140", features = ["derive"]}
Expand Down
101 changes: 61 additions & 40 deletions redash-search-sync/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,73 @@
use anyhow::Result;
use chrono::{DateTime, Local};
use once_cell::sync::Lazy;
use opensearch::BulkParts;
use opensearch::{http::request::JsonBody, OpenSearch};
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_json::{json, Value};

use crate::configs::Configs;
use crate::redash::{self, RedashClient};

const REDASH_INDEX_NAME: &str = "redash";

static INDEX_CONFIG: Lazy<Value> = Lazy::new(|| {
json!({
"settings": {
"analysis": {
"analyzer": {
"sql_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase"
],
"char_filter": [
"sql_char_filter"
]
}
},
"char_filter": {
"sql_char_filter": {
"type": "pattern_replace",
"pattern": "[\\.]",
"replacement": " "
}
}
}
},
"mappings": {
"properties": {
"query": {
"type": "text",
"analyzer": "sql_analyzer"
},
"created_at": {
"type": "date",
"format": "date_time"
},
"updated_at": {
"type": "date",
"format": "date_time"
},
}
}
})
});

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct RedashDocument {
id: i32,
name: String,
query: String,
updated_at: String,
created_at: String,
user_name: String,
user_email: String,
created_at: DateTime<Local>,
updated_at: DateTime<Local>,
data_source_id: i32,
data_source_name: String,
data_source_type: String,
tags: Vec<String>,
url: String,
}

pub struct App {
Expand Down Expand Up @@ -62,42 +111,9 @@ impl App {
.create(opensearch::indices::IndicesCreateParts::Index(
REDASH_INDEX_NAME,
))
.body(json!({
"settings": {
"analysis": {
"analyzer": {
"sql_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase"
],
"char_filter": [
"sql_char_filter"
]
}
},
"char_filter": {
"sql_char_filter": {
"type": "pattern_replace",
"pattern": "[\\._]",
"replacement": " "
}
}
}
},
"mappings": {
"properties": {
"query": {
"type": "text",
"analyzer": "sql_analyzer"
}
}
}
}))
.body(INDEX_CONFIG.clone())
.send()
.await?;

if !res.status_code().is_success() {
tracing::error!(
response = res.text().await.unwrap(),
Expand All @@ -113,6 +129,7 @@ impl App {

// TODO: sync only updated queries
pub async fn sync(&self) -> Result<()> {
let data_sources = self.redash_client.get_data_sources().await?;
let res = self
.redash_client
.get_queries(redash::GetQueriesRequest {
Expand All @@ -123,15 +140,19 @@ impl App {
.await?;
let mut body: Vec<JsonBody<_>> = Vec::new();
for query in res.results {
let data_source = data_sources.get(&query.data_source_id).unwrap();
let doc = RedashDocument {
id: query.id,
name: query.name,
query: query.query,
updated_at: query.updated_at,
user_name: query.user.name,
user_email: query.user.email,
created_at: query.created_at,
updated_at: query.updated_at,
data_source_id: query.data_source_id,
data_source_name: data_source.name.clone(),
data_source_type: data_source.r#type.clone(),
tags: query.tags,
url: format!("{}/queries/{}", self.configs.redash.url, query.id),
};
body.push(
json!({
Expand Down
2 changes: 1 addition & 1 deletion redash-search-sync/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tracing::Level;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_max_level(Level::INFO)
.with_max_level(Level::DEBUG)
.with_level(true)
.with_target(true)
.with_thread_ids(true)
Expand Down
67 changes: 64 additions & 3 deletions redash-search-sync/src/redash.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::collections::HashMap;

use anyhow::Result;
use async_trait::async_trait;

use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};

#[async_trait]
pub trait RedashClient {
async fn get_queries(&self, req: GetQueriesRequest) -> Result<GetQueriesResponse>;
async fn get_data_sources(&self) -> Result<GetDataSourcesResponse>;
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
Expand All @@ -20,16 +24,29 @@ pub struct GetQueriesRequest {
pub struct RedashQuery {
pub id: i32,
pub name: String,
pub description: Option<String>,
pub user: RedashUser,
pub query: String,
pub query_hash: String,
pub is_archived: bool,
pub is_draft: bool,
pub updated_at: String,
pub created_at: String,
pub created_at: DateTime<Local>,
pub updated_at: DateTime<Local>,
pub data_source_id: i32,
pub tags: Vec<String>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct RedashUser {
pub id: i32,
pub name: String,
pub email: String,
pub is_disabled: bool,
pub is_invitation_pending: bool,
pub updated_at: String,
pub created_at: String,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct GetQueriesResponse {
pub count: i32,
Expand All @@ -38,6 +55,20 @@ pub struct GetQueriesResponse {
pub results: Vec<RedashQuery>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct RedashDataSource {
pub id: i32,
pub name: String,
pub r#type: String,
pub syntax: String,
pub paused: i32,
pub pause_reason: Option<String>,
pub supports_auto_limit: bool,
pub view_only: bool,
}

type GetDataSourcesResponse = HashMap<i32, RedashDataSource>;

/// This is a default implementation of RedashClient
/// NOTE: some fields are omitted for simplicity
///
Expand Down Expand Up @@ -73,11 +104,41 @@ impl RedashClient for DefaultRedashClient {

let res = req.send().await?;
if res.status().is_success() {
Ok(res.json().await?)
// TODO: sometimes status is 200 but response shows error
// e.g. "{"took":6,"errors":true, ...}"
// so we need to handle this case
let data = res.json().await?;
tracing::debug!(data = serde_json::to_string(&data).unwrap(), "Got queries");
Ok(data)
} else {
let data = res.text().await.unwrap();
tracing::error!(data = data, "Failed to get queries");
Err(anyhow::anyhow!("Failed to get queries"))
}
}

async fn get_data_sources(&self) -> Result<GetDataSourcesResponse> {
let client = reqwest::Client::new();
let req = client
.get(format!("{}/api/data_sources", self.base_url))
.header("Authorization", format!("Key {}", self.api_key));

let res = req.send().await?;
if res.status().is_success() {
let data = res.json::<Vec<RedashDataSource>>().await?;
tracing::debug!(
data = serde_json::to_string(&data).unwrap(),
"Got data sources"
);
let data = data
.into_iter()
.map(|ds| (ds.id, ds))
.collect::<GetDataSourcesResponse>();
Ok(data)
} else {
let data = res.text().await.unwrap();
tracing::error!(data = data, "Failed to get data sources");
Err(anyhow::anyhow!("Failed to get data sources"))
}
}
}
65 changes: 58 additions & 7 deletions redash-search-web/components/HitList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import {
EuiFlexGrid,
EuiFlexItem,
EuiCard,
EuiFlexGroup,
EuiTitle,
EuiText,
EuiSpacer,
EuiButtonEmpty,
EuiButton,
EuiLink,
EuiDescriptionList,
EuiFlexGroup,
EuiAvatar,
} from "@elastic/eui";
import { IResultHitItem } from "../pages/api/models";
import { HighlightedQuery } from "./HighlightedQuery";
import getConfig from "next/config";

const { publicRuntimeConfig } = getConfig();

export interface HitListProps {
hitItems: IResultHitItem[];
Expand All @@ -25,9 +25,60 @@ const HitsList: React.FC<HitListProps> = ({ hitItems }) => {
<EuiFlexItem key={hit.id} grow={false}>
<EuiCard
layout="horizontal"
icon={
<EuiAvatar
name={`logo-${hit.fields.data_source_type}`}
size="l"
imageUrl={`${publicRuntimeConfig.redashURL}/static/images/db-logos/${hit.fields.data_source_type}.png`}
color="#e0e5ee"
/>
}
title={hit.fields.name}
href={hit.fields.url}
href={`${publicRuntimeConfig.redashURL}/queries/${hit.id}`}
description={hit.fields.description}
>
<EuiFlexGrid gutterSize="xl">
<EuiFlexGroup>
<EuiFlexItem grow={5}>
<EuiDescriptionList
textStyle="reverse"
listItems={[
{
title: "Data Source Type",
description: hit.fields.data_source_type,
},
{
title: "Author",
description: hit.fields.user_name,
},
{
title: "Tags",
description: hit.fields.tags.join(", ") || "No Tags",
},
]}
/>
</EuiFlexItem>
<EuiFlexItem grow={5}>
<EuiDescriptionList
textStyle="reverse"
listItems={[
{
title: "Data Source Name",
description: hit.fields.data_source_name,
},
{
title: "Created At",
description: hit.fields.created_at,
},
{
title: "Updated At",
description: hit.fields.updated_at,
},
]}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGrid>
{hit.highlight.query && (
<>
<EuiSpacer />
Expand Down
5 changes: 5 additions & 0 deletions redash-search-web/next.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
const { processEnv } = require("@next/env");

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
compiler: {
emotion: true,
},
publicRuntimeConfig: {
redashURL: processEnv.REDASH__URL.replace(/\/$/, ""),
},
};

module.exports = nextConfig;
2 changes: 1 addition & 1 deletion redash-search-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"graphql": "16.6.0",
"micro": "^10.0.1",
"micro-cors": "^0.1.1",
"moment": "2.29.1",
"moment": "^2.29.4",
"next": "13.1.1",
"next-with-apollo": "5.1.0",
"react": "18.2.0",
Expand Down
Loading