Source code for gs_quant.analytics.workspaces.components

"""
Copyright 2019 Goldman Sachs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
"""

import uuid
from abc import ABC, abstractmethod
from enum import Enum
from typing import Dict, List, Optional

from pydash import unset, snake_case


class Selection:
    def __init__(self,
                 selector_id: str,
                 tag: str):
        """
        Selection option.
        :param selector_id: identifier of the selector which applies to this selection
        :param tag: tag to match in the selector. Will show up as the option in the associated selector.
        """
        self.__selector_id = selector_id
        self.__tag = tag

    @property
    def selector_id(self):
        return self.__selector_id

    @selector_id.setter
    def selector_id(self, value):
        self.__selector_id = value

    @property
    def tag(self):
        return self.__tag

    @tag.setter
    def tag(self, value):
        self.__tag = value

    def as_dict(self):
        return {
            'selectorId': self.__selector_id,
            'tag': self.__tag
        }

    @classmethod
    def from_dict(cls, obj):
        return Selection(obj['selectorId'], obj['tag'])


class LegendItem:
    def __init__(self,
                 color: str,
                 icon: str,
                 name: str,
                 tooltip: str = None):
        """
        Item in the legend component
        :param color: color of the legend item
        :param icon: icon of the legend
        :param name: name of the legend item
        :param tooltip: tooltip to display on the the name
        """
        self.color = color
        self.icon = icon
        self.name = name
        self.tooltip = tooltip

    def as_dict(self):
        dict_ = {
            'color': self.color,
            'icon': self.icon,
            'name': self.name
        }
        if self.tooltip:
            dict_['tooltip'] = self.tooltip
        return dict_

    @classmethod
    def from_dict(cls, obj):
        return LegendItem(color=obj['color'], icon=obj['icon'], name=obj['name'], tooltip=obj.get('tooltip'))


class RelatedLinkType(Enum):
    anchor = 'anchor'
    internal = 'internal'
    external = 'external'
    mail = 'mail'
    notification = 'notification'


class RelatedLink:
    def __init__(self,
                 type_: RelatedLinkType,
                 name: str,
                 link: str,
                 description: str = None):
        """
        Related Link Item
        :param type_: Type of the Related Link
        :param name: Name that appears on the related links
        :param link: link to navigate when the item is clicked
        :param description: description of the link
        """
        self.type_ = type_
        self.name = name
        self.link = link
        self.description = description
        # TODO: self.notification_properties

    def as_dict(self):
        dict_ = {
            'type': self.type_.value,
            'name': self.name,
            'link': self.link
        }
        if self.description:
            dict_['description'] = self.description
        return dict_

    @classmethod
    def from_dict(cls, obj):
        return RelatedLink(type_=RelatedLinkType(obj['type']), name=obj['name'], link=obj['link'],
                           description=obj.get('description'))


class PromoSize(Enum):
    DEFAULT = 'default'
    LARGE = 'large'


[docs]class Component(ABC):
[docs] def __init__(self, height: Optional[int] = None, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, container_ids: List[str] = None): self.__id = id_ or f'{self.__class__.__name__}-{str(uuid.uuid4())[0:5]}' self._height = height self.__width = width self.__selections = selections self.__container_ids = container_ids self._type = None
@property def id_(self): return self.__id @id_.setter def id_(self, value): self.__id = value or f'{self.__class__.__name__}-{str(uuid.uuid4())[0:5]}' @property def width(self): return self.__width @width.setter def width(self, value): self.__width = value @property def height(self): return self._height @height.setter def height(self, value): self._height = value @property def selections(self): return self.__selections @selections.setter def selections(self, value): self.__selections = value @property def container_ids(self): return self.__container_ids @container_ids.setter def container_ids(self, value): self.__container_ids = value
[docs] @abstractmethod def as_dict(self) -> Dict: dict_ = { 'id': self.__id, 'type': self._type, 'parameters': { 'height': self._height or 200 } } if self.__selections: dict_['selections'] = [selection.as_dict() for selection in self.__selections] if self.__container_ids: dict_['containerIds'] = [containerId for containerId in self.__container_ids] return dict_
[docs] @classmethod def from_dict(cls, obj, scale: int = None): parameters = obj.get('parameters', {}) height = parameters.get('height', 200) unset(parameters, 'height') unset(parameters, 'width') component = TYPE_TO_COMPONENT[obj['type']](id_=obj['id'], height=height, width=scale, **{snake_case(k): v for k, v in parameters.items()}) selections, container_ids, tags = obj.get('selections'), obj.get('containerIds'), obj.get('tags') if selections: component.selections = [Selection.from_dict(selection) for selection in selections] if container_ids: component.__container_ids = [containerId for containerId in container_ids] if tags: component.tags = tags return component
class PlotComponent(Component): def __init__(self, height: int, id_: str, *, width: int = None, selections: List[Selection] = None, tooltip: str = None, hide_legend: bool = False): """ Plot Component :param id_: identifier of the plot :param height: height of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip on the chart name :param hide_legend: whether to hide the series legend under the plot """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'plot' self.tooltip = tooltip self.hide_legend = hide_legend def as_dict(self) -> Dict: dict_ = super().as_dict() dict_['parameters']['hideLegend'] = self.hide_legend if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip return dict_ class DataVizComponent(Component): def __init__(self, height: int, id_: str, *, width: int = None): """ Data Visualization Component :param id_: identifier of the Visualization :param height: height of the componentTYPE_TO_COMPONENT :param width: width of the component integers 1-12 """ super().__init__(id_=id_, height=height, width=width) self._type = 'dataviz' def as_dict(self) -> Dict: return super().as_dict() class DataGridComponent(Component): def __init__(self, height: int, id_: str, *, width: int = None, selections: List[Selection] = None, tooltip: str = None): """ DataGrid Component :param height: height of the component :param id_: unique identifier of the DataGrid :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip on the DataGrid name """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'datagrid' self.tooltip = tooltip def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip return dict_ class DataScreenerComponent(Component): def __init__(self, height: int, id_: str, *, width: int = None, tooltip: str = None): """ Data Screener Component :param height: height of the component :param id_: unique identifier of the Data Screener :param width: width of the component integers 1-12 :param tooltip: text to show in a tooltip on the Data Screener name """ super().__init__(id_=id_, height=height, width=width) self._type = 'screener' self.tooltip = tooltip def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip return dict_ class ArticleComponent(Component): def __init__(self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, tooltip: str = None, commentary_channels: List[str] = None, commentary_to_desktop_link: bool = None): """ Article Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip on the article component name :param commentary_channels: List of commentary channels that provides data :param commentary_to_desktop_link: Whether or not to display a link from commentary to desktop in the header """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'article' self.tooltip = tooltip self.commentary_channels = commentary_channels self.commentary_to_desktop_link = commentary_to_desktop_link def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip if self.commentary_channels: dict_['parameters']['commentaryChannels'] = self.commentary_channels if self.commentary_to_desktop_link: dict_['parameters']['commentaryToDesktopLink'] = self.commentary_to_desktop_link return dict_ class CommentaryComponent(Component): def __init__(self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, tooltip: str = None, commentary_channels: List[str] = None, commentary_to_desktop_link: bool = None): """ Commentary Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip on the article component name :param commentary_channels: List of commentary channels that provides data :param commentary_to_desktop_link: Whether or not to display a link from commentary to desktop in the header """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'plot' self.tooltip = tooltip self.commentary_channels = commentary_channels self.commentary_to_desktop_link = commentary_to_desktop_link def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip if self.commentary_channels: dict_['parameters']['commentaryChannels'] = self.commentary_channels if self.commentary_to_desktop_link: dict_['parameters']['commentaryToDesktopLink'] = self.commentary_to_desktop_link return dict_ class ContainerComponent(Component): def __init__(self, id_: Optional[str] = None, *, width: int = None, component_id: str = None): """ Container Component which acts as a placeholder for components used with selectors :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param component_id: default component id to use in the container """ super().__init__(id_=id_, width=width, selections=None) self._type = 'container' self.component_id = component_id def as_dict(self) -> Dict: dict_ = super().as_dict() if self.component_id: dict_['parameters']['componentId'] = self.component_id del dict_['parameters']['height'] return dict_ class SelectorComponent(Component): def __init__(self, height: int, id_: Optional[str] = None, *, container_ids: List[str], width: int = None, title: str = None, default_option_index: int = None, tooltip: str = None, parent_selector_id: str = None): """ Selector Component to conditionally pick components based on their selection tags. :param height: height of the component :param id_: unique identifier of the component :param container_ids: Name of containers affected by the selector :param width: width of the component integers 1-12 :param title: Text to show next to selector dropdown :param default_option_index: default index of the dropdown :param tooltip: text to show in tooltip on the title :param parent_selector_id: unique identifier of the parent selector component for nested selections """ super().__init__(id_=id_, height=height, width=width, selections=None) self._type = 'selector' self.container_ids = container_ids self.title = title self.default_option_index = default_option_index self.tooltip = tooltip self.parent_selector_id = parent_selector_id def as_dict(self) -> Dict: dict_ = super().as_dict() dict_['parameters']['containerIds'] = self.container_ids if self.default_option_index: dict_['parameters']['defaultOptionIndex'] = self.default_option_index if self.title: dict_['parameters']['title'] = self.title if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip if self.parent_selector_id: dict_['parameters']['parentSelectorId'] = self.parent_selector_id return dict_ @classmethod def from_dict(cls, obj, scale: int = None): parameters = obj.get('parameters', {}) return SelectorComponent(id_=obj['id'], height=parameters.get('height', 200), width=scale, title=parameters.get('title'), container_ids=parameters['containerIds'], tooltip=parameters.get('tooltip'), default_option_index=parameters.get('defaultOptionIndex'), parent_selector_id=parameters.get('parentSelectorId')) class PromoComponent(Component): def __init__(self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, tooltip: str = None, transparent: bool = None, body: str = None, size: PromoSize = None, hide_border: bool = None): """ Promo Component for arbitrary text :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in tooltip on the title :param transparent: whether or not the background of the component is transparent :param body: text to show in the component that can have html tags :param size: size of the component text :param hide_border: whether to hide the border of the component """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'promo' self.tooltip = tooltip self.transparent = transparent self.body = body self.size = size self.hide_border = hide_border def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip if self.body: dict_['parameters']['body'] = self.body if self.size: dict_['parameters']['size'] = self.size.value if self.hide_border is not None: dict_['parameters']['hideBorder'] = self.size if self.transparent is not None: dict_['parameters']['transparent'] = self.transparent return dict_ @classmethod def from_dict(cls, obj: Dict, scale: int = None): parameters = obj.get('parameters', {}) size = parameters.get('size') size = PromoSize(size) if size else None return PromoComponent(id_=obj['id'], height=parameters.get('height', 200), width=scale, tooltip=parameters.get('tooltip'), body=parameters.get('body'), size=size, hide_border=parameters.get('hideBorder')) class SeparatorComponent(Component): def __init__(self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, name: str = None, size: str = None, show_more_url: str = None): """ Separator Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param name: Text of the separator :param size: size of the component :param show_more_url: Url link to redirect """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'separator' self.name = name self.size = size self.show_more_url = show_more_url def as_dict(self) -> Dict: dict_ = super().as_dict() if self.name: dict_['parameters']['name'] = self.name if self.size: dict_['parameters']['size'] = self.size if self.show_more_url: dict_['parameters']['showMoreUrl'] = self.show_more_url return dict_ class LegendComponent(Component): def __init__(self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, items: List[LegendItem] = None, position: str = None, transparent: bool = None): """ Legend Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param items: Legend items to appear in the legend :param position: position of the legend :param transparent: Whether the background of the legend is transparent """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'legend' self.items = items self.position = position self.transparent = transparent def as_dict(self) -> Dict: dict_ = super().as_dict() dict_['parameters']['items'] = [item.as_dict() for item in self.items] if self.position: dict_['parameters']['position'] = self.position if self.transparent: dict_['parameters']['transparent'] = self.transparent return dict_ @classmethod def from_dict(cls, obj: Dict, scale: int = None): parameters = obj.get('parameters', {}) items = [LegendItem.from_dict(item) for item in parameters.get('items', [])] return LegendComponent(id_=obj['id'], height=parameters.get('height', 200), width=scale, selections=obj.get('selections'), position=parameters.get('position'), transparent=parameters.get('transparent'), items=items) class MonitorComponent(Component): def __init__(self, height: int, id_: str, *, width: int = None, selections: List[Selection] = None, tooltip: str = None): """ Monitor Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param tooltip: text to show in a tooltip when hovering over monitor name """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'monitor' self.tooltip = tooltip def as_dict(self) -> Dict: dict_ = super().as_dict() if self.tooltip: dict_['parameters']['tooltip'] = self.tooltip return dict_ class RelatedLinksComponent(Component): def __init__(self, height: int, id_: Optional[str] = None, *, width: int = None, selections: List[Selection] = None, links: List[RelatedLink], title: str): """ Related Links Component :param height: height of the component :param id_: unique identifier of the component :param width: width of the component integers 1-12 :param selections: List of selections used for selectors :param links: links to add to the component :param title: title of the component """ super().__init__(id_=id_, height=height, width=width, selections=selections) self._type = 'relatedLinks' self.links = links self.title = title def as_dict(self) -> Dict: dict_ = super().as_dict() dict_['parameters']['title'] = self.title dict_['parameters']['links'] = [link.as_dict() for link in self.links] return dict_ @classmethod def from_dict(cls, obj, scale: int = None): parameters = obj.get('parameters', {}) return RelatedLinksComponent(id_=obj['id'], height=parameters.get('height', 200), width=scale, selections=obj.get('selections'), title=parameters['title'], links=[RelatedLink.from_dict(link) for link in parameters['links']]) TYPE_TO_COMPONENT = { 'article': ArticleComponent, 'container': ContainerComponent, 'datagrid': DataGridComponent, 'dataviz': DataVizComponent, 'legend': LegendComponent, 'monitor': MonitorComponent, 'plot': PlotComponent, 'promo': PromoComponent, 'relatedLinks': RelatedLinksComponent, 'selector': SelectorComponent, 'separator': SeparatorComponent, 'screener': DataScreenerComponent }