diff --git a/jest.config.js b/jest.config.js index 4611efed7..ddb8727ad 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,7 @@ const config = { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/__mocks__/fileMock.js', '.*\\.(css|less|scss)$': '/__mocks__/styleMock.js', + 'config/color_palette.yaml': '/../config/color_palette.yaml', }, moduleDirectories: [ 'node_modules', // This is required @@ -22,6 +23,7 @@ const config = { transform: { '^.+\\.(jsx|js|ts|tsx)$': 'babel-jest', '\\.m?js?$': 'jest-esm-transformer', + '\\.(yaml|yml)$': '/jest/yaml-transformer.js', }, }; diff --git a/package.json b/package.json index 48f3dc04c..8522a0e2d 100644 --- a/package.json +++ b/package.json @@ -168,6 +168,7 @@ "eslint-plugin-unused-imports": "^2.0.0", "jest": "26.6.3", "jest-esm-transformer": "^1.0.0", + "js-yaml": "^4.1.0", "postcss-loader": "^6.1.0", "postcss-preset-env": "6.7.0", "prettier": "2.7.1", diff --git a/querybook/config/color_palette.yaml b/querybook/config/color_palette.yaml new file mode 100644 index 000000000..9609d0922 --- /dev/null +++ b/querybook/config/color_palette.yaml @@ -0,0 +1,48 @@ +- name: blue + color: '#35B5BB' + fillColor: rgba(53, 181, 187, 0.25) +- name: pink + color: '#ff3975' + fillColor: rgba(255, 57, 117, 0.25) +- name: grey + color: '#bfbfbf' + fillColor: rgba(191, 191, 191, 0.25) +- name: gold + color: '#ffca00' + fillColor: rgba(255, 202, 0, 0.25) +- name: picton blue + color: '#529dce' + fillColor: rgba(82, 157, 206, 0.25) +- name: orange + color: '#ff9f42' + fillColor: rgba(255, 159, 66, 0.25) +- name: creamy forest green + color: '#6ba097' + fillColor: rgba(107, 160, 151, 0.25) +- name: chartreuse + color: '#aee800' + fillColor: rgba(174, 232, 0, 0.25) +- name: baby pink + color: '#ff91c8' + fillColor: rgba(255, 145, 200, 0.25) +- name: icy blue + color: '#85d0ce' + fillColor: rgba(133, 208, 206, 0.25) +- name: fuscia + color: '#eb37ce' + fillColor: rgba(235, 55, 206, 0.25) +- name: light purple + color: '#C792EA' + fillColor: rgba(199, 146, 234, 0.25) +- name: salmon + color: '#ec7f77' + fillColor: rgba(236, 127, 119, 0.25) +- name: olive + color: '#989801' + fillColor: rgba(152, 152, 1, 0.25) +- name: beige + color: '#ecd1af' + fillColor: rgba(236, 209, 175, 0.25) +- name: choco + color: '#b7652b' + fillColor: rgba(183, 101, 43, 0.25) diff --git a/querybook/migrations/versions/ec2f32c25f34_add_more_metadata_support.py b/querybook/migrations/versions/ec2f32c25f34_add_more_metadata_support.py index d877a0bca..839b031d5 100644 --- a/querybook/migrations/versions/ec2f32c25f34_add_more_metadata_support.py +++ b/querybook/migrations/versions/ec2f32c25f34_add_more_metadata_support.py @@ -7,7 +7,6 @@ """ from alembic import op import sqlalchemy as sa -from sqlalchemy.dialects import mysql # revision identifiers, used by Alembic. revision = 'ec2f32c25f34' diff --git a/querybook/server/const/color.py b/querybook/server/const/color.py new file mode 100644 index 000000000..d2518bd76 --- /dev/null +++ b/querybook/server/const/color.py @@ -0,0 +1,8 @@ +from typing import TypedDict + + +class PaletteColor(TypedDict): + name: str + # color in hex format, e.g. #4287f5 + color: str + fillColor: str diff --git a/querybook/server/const/metastore.py b/querybook/server/const/metastore.py index a299f357c..cdabe2ca1 100644 --- a/querybook/server/const/metastore.py +++ b/querybook/server/const/metastore.py @@ -11,6 +11,7 @@ class DataTag(NamedTuple): # below properties will be stored in tag.meta type: str = None description: str = None + # color in hex format, e.g. #4287f5 color: str = None diff --git a/querybook/server/lib/utils/color.py b/querybook/server/lib/utils/color.py new file mode 100644 index 000000000..11d99ccbd --- /dev/null +++ b/querybook/server/lib/utils/color.py @@ -0,0 +1,44 @@ +import math + +from const.color import PaletteColor +from lib.config import get_config_value + + +color_palette = get_config_value("color_palette") + + +def convert_hex_to_rgb(hex_color: str) -> tuple[int, int, int]: + r = int(hex_color[1:3], 16) + g = int(hex_color[3:5], 16) + b = int(hex_color[5:7], 16) + return (r, g, b) + + +def find_nearest_palette_color(hex_color: str) -> PaletteColor: + """Given a hex color, find the nearest color from the color palette.""" + # Return the given color if it's in the color palette + exact_color = next( + (color for color in color_palette if color["color"] == "#529dce"), None + ) + if exact_color: + return exact_color + + # Convert the hex color to RGB + given_rgb_color = convert_hex_to_rgb(hex_color) + + # Calculate the Euclidean distance between the given color and each color in the palette + min_distance = math.inf + nearest_color = None + for color in color_palette: + platte_hex_color = color["color"] + palette_rgb_color = convert_hex_to_rgb(platte_hex_color) + distance = ( + (given_rgb_color[0] - palette_rgb_color[0]) ** 2 + + (given_rgb_color[1] - palette_rgb_color[1]) ** 2 + + (given_rgb_color[2] - palette_rgb_color[2]) ** 2 + ) + if distance < min_distance: + min_distance = distance + nearest_color = color + + return nearest_color diff --git a/querybook/server/lib/utils/json.py b/querybook/server/lib/utils/json.py index 7ab7351c4..c8fb2250b 100644 --- a/querybook/server/lib/utils/json.py +++ b/querybook/server/lib/utils/json.py @@ -20,6 +20,9 @@ def __init__(self, *args, **kwargs): def default(self, obj): # pylint: disable=E0202 if hasattr(obj, "to_dict"): return obj.to_dict() + # check for NamedTuple + elif isinstance(obj, tuple) and hasattr(obj, "_fields"): + return obj._asdict() elif isinstance(obj, datetime): return self.datetime_formatter(obj) elif isinstance(obj, date): diff --git a/querybook/server/logic/tag.py b/querybook/server/logic/tag.py index fb8296ecd..77c66d576 100644 --- a/querybook/server/logic/tag.py +++ b/querybook/server/logic/tag.py @@ -2,6 +2,7 @@ from app.db import with_session from const.metastore import DataTag +from lib.utils.color import find_nearest_palette_color from logic.metastore import update_es_tables_by_id from models.tag import Tag, TagItem @@ -119,7 +120,7 @@ def create_table_tags( meta = { "type": tag.type, "tooltip": tag.description, - "color": tag.color, + "color": find_nearest_palette_color(tag.color)["name"], "admin": True, } # filter out properties with none values @@ -157,7 +158,7 @@ def create_column_tags( meta = { "type": tag.type, "tooltip": tag.description, - "color": tag.color, + "color": find_nearest_palette_color(tag.color)["name"], "admin": True, } # filter out properties with none values diff --git a/querybook/server/models/admin.py b/querybook/server/models/admin.py index cbfe7ebea..bb32488ca 100644 --- a/querybook/server/models/admin.py +++ b/querybook/server/models/admin.py @@ -164,7 +164,7 @@ def to_dict(self): "id": self.id, "name": self.name, "config": loader_class.loader_config.to_dict(), - "owner_types": loader_class.get_table_owner_types(), + "owner_types": [t._asdict() for t in loader_class.get_table_owner_types()], } def to_dict_admin(self): diff --git a/querybook/webapp/config.d.ts b/querybook/webapp/config.d.ts index 9901e7d28..345d3a415 100644 --- a/querybook/webapp/config.d.ts +++ b/querybook/webapp/config.d.ts @@ -74,3 +74,12 @@ declare module 'config/query_error.yaml' { >; export default data; } + +declare module 'config/color_palette.yaml' { + const data: Array<{ + name: string; + color: string; + fillColor: string; + }>; + export default data; +} diff --git a/querybook/webapp/const/chartColors.ts b/querybook/webapp/const/chartColors.ts index 3f6856628..6c86355e0 100644 --- a/querybook/webapp/const/chartColors.ts +++ b/querybook/webapp/const/chartColors.ts @@ -1,54 +1,6 @@ -interface IColorPalette { - name: string; - color: string; - fillColor: string; -} -export const ColorPalette: IColorPalette[] = [ - { name: 'blue', color: '#35B5BB', fillColor: 'rgba(53, 181, 187, 0.25)' }, - { name: 'pink', color: '#ff3975', fillColor: 'rgba(255, 57, 117, 0.25)' }, - { name: 'grey', color: '#bfbfbf', fillColor: 'rgba(191, 191, 191, 0.25)' }, - { name: 'gold', color: '#ffca00', fillColor: 'rgba(255, 202, 0, 0.25)' }, - { - name: 'picton blue', - color: '#529dce', - fillColor: 'rgba(82, 157, 206, 0.25)', - }, - { name: 'orange', color: '#ff9f42', fillColor: 'rgba(255, 159, 66, 0.25)' }, - { - name: 'creamy forest green', - color: '#6ba097', - fillColor: 'rgba(107, 160, 151, 0.25)', - }, - { - name: 'chartreuse', - color: '#aee800', - fillColor: 'rgba(174, 232, 0, 0.25)', - }, - { - name: 'baby pink', - color: '#ff91c8', - fillColor: 'rgba(255, 145, 200, 0.25)', - }, - { - name: 'icy blue', - color: '#85d0ce', - fillColor: 'rgba(133, 208, 206, 0.25)', - }, - { name: 'fuscia', color: '#eb37ce', fillColor: 'rgba(235, 55, 206, 0.25)' }, - { - name: 'light purple', - color: '#C792EA', - fillColor: 'rgba(199, 146, 234, 0.25)', - }, - { - name: 'salmon', - color: '#ec7f77', - fillColor: 'rgba(236, 127, 119, 0.25)', - }, - { name: 'olive', color: '#989801', fillColor: 'rgba(152, 152, 1, 0.25)' }, - { name: 'beige', color: '#ecd1af', fillColor: 'rgba(236, 209, 175, 0.25)' }, - { name: 'choco', color: '#b7652b', fillColor: 'rgba(183, 101, 43, 0.25)' }, -]; +import colorPalette from 'config/color_palette.yaml'; + +export const ColorPalette = colorPalette; // rgb for css vars - font export const fontColor = { diff --git a/querybook/webapp/jest/yaml-transformer.js b/querybook/webapp/jest/yaml-transformer.js new file mode 100644 index 000000000..fd37f791f --- /dev/null +++ b/querybook/webapp/jest/yaml-transformer.js @@ -0,0 +1,11 @@ +const yaml = require('js-yaml'); + +module.exports = { + process: (src, filename) => { + if (filename.endsWith('.yaml') || filename.endsWith('.yml')) { + const data = yaml.load(src); + return `module.exports = ${JSON.stringify(data)};`; + } + return src; + }, +};