<commit_before>import React, { useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types";
import { find, has } from "lodash";
import { react2angular } from "react2angular";
import moment from "moment";
import { markdown } from "markdown";
import Button from "antd/lib/button";
import Dropdown from "antd/lib/dropdown";
import Icon from "antd/lib/icon";
import Menu from "antd/lib/menu";
import Tooltip from "antd/lib/tooltip";
import { $location, $routeParams } from "@/services/ng";
import { formatDateTime } from "@/filters/datetime";
import HtmlContent from "@/components/HtmlContent";
import { Parameters } from "@/components/Parameters";
import { Moment } from "@/components/proptypes";
import { TimeAgo } from "@/components/TimeAgo";
import { Timer } from "@/components/Timer";
import QueryResultsLink from "@/components/EditVisualizationButton/QueryResultsLink";
import VisualizationName from "@/visualizations/VisualizationName";
import { VisualizationRenderer } from "@/visualizations/VisualizationRenderer";
import { VisualizationType } from "@/visualizations";

import logoUrl from "@/assets/images/redash_icon_small.png";

function VisualizationEmbedHeader({ queryName, queryDescription, visualization }) {
  return (
    <div className="embed-heading p-b-10 p-r-15 p-l-15">
      <h3>
        <img src={logoUrl} alt="Redash Logo" style={{ height: "24px", verticalAlign: "text-bottom" }} />
        <VisualizationName visualization={visualization} /> {queryName}
        {queryDescription && (
          <small>
            <HtmlContent className="markdown text-muted">{markdown.toHTML(queryDescription || "")}</HtmlContent>
          </small>
        )}
      </h3>
    </div>
  );
}

VisualizationEmbedHeader.propTypes = {
  queryName: PropTypes.string.isRequired,
  queryDescription: PropTypes.string,
  visualization: VisualizationType.isRequired,
};

VisualizationEmbedHeader.defaultProps = { queryDescription: "" };

function VisualizationEmbedFooter({ query, queryResults, updatedAt, refreshStartedAt, queryUrl }) {
  const downloadMenu = (
    <Menu>
      <Menu.Item>
        <QueryResultsLink
          fileType="csv"
          query={query}
          queryResult={queryResults}
          apiKey={$routeParams.api_key}
          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}
          embed>
          <Icon type="file" /> Download as CSV File
        </QueryResultsLink>
      </Menu.Item>
      <Menu.Item>
        <QueryResultsLink
          fileType="tsv"
          query={query}
          queryResult={queryResults}
          apiKey={$routeParams.api_key}
          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}
          embed>
          <Icon type="file" /> Download as TSV File
        </QueryResultsLink>
      </Menu.Item>
      <Menu.Item>
        <QueryResultsLink
          fileType="xlsx"
          query={query}
          queryResult={queryResults}
          apiKey={$routeParams.api_key}
          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}
          embed>
          <Icon type="file-excel" /> Download as Excel File
        </QueryResultsLink>
      </Menu.Item>
    </Menu>
  );

  return (
    <div className="tile__bottom-control">
      <span>
        <a className="small hidden-print">
          <i className="zmdi zmdi-time-restore" />{" "}
          {refreshStartedAt ? <Timer from={refreshStartedAt} /> : <TimeAgo date={updatedAt} />}
        </a>
        <span className="small visible-print">
          <i className="zmdi zmdi-time-restore" /> {formatDateTime(updatedAt)}
        </span>
      </span>
      {queryUrl && (
        <span className="hidden-print">
          <Tooltip title="Open in Redash">
            <Button className="icon-button" href={queryUrl} target="_blank">
              <i className="fa fa-external-link" />
            </Button>
          </Tooltip>
          {!query.hasParameters() && (
            <Dropdown overlay={downloadMenu} disabled={!queryResults} trigger={["click"]} placement="topLeft">
              <Button loading={!queryResults && !!refreshStartedAt} className="m-l-5">
                Download Dataset
                <i className="fa fa-caret-up m-l-5" />
              </Button>
            </Dropdown>
          )}
        </span>
      )}
    </div>
  );
}

VisualizationEmbedFooter.propTypes = {
  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  queryResults: PropTypes.object, // eslint-disable-line react/forbid-prop-types
  updatedAt: PropTypes.string,
  refreshStartedAt: Moment,
  queryUrl: PropTypes.string,
};

VisualizationEmbedFooter.defaultProps = {
  queryResults: null,
  updatedAt: null,
  refreshStartedAt: null,
  queryUrl: null,
};

function VisualizationEmbed({ query }) {
  const [error, setError] = useState(null);
  const [refreshStartedAt, setRefreshStartedAt] = useState(null);
  const [queryResults, setQueryResults] = useState(null);
  const hideHeader = has($location.search(), "hide_header");
  const hideParametersUI = has($location.search(), "hide_parameters");
  const hideQueryLink = has($location.search(), "hide_link");

  const showQueryDescription = has($location.search(), "showDescription");
  const visualizationId = parseInt($routeParams.visualizationId, 10);
  const visualization = find(query.visualizations, vis => vis.id === visualizationId);

  const refreshQueryResults = useCallback(() => {
    setError(null);
    setRefreshStartedAt(moment());
    query
      .getQueryResultPromise()
      .then(result => {
        setQueryResults(result);
      })
      .catch(err => {
        setError(err.getError());
      })
      .finally(() => setRefreshStartedAt(null));
  }, [query]);

  useEffect(() => {
    document.querySelector("body").classList.add("headless");
    refreshQueryResults();
  }, [refreshQueryResults]);

  return (
    <div className="tile m-l-10 m-r-10 p-t-10 embed__vis" data-test="VisualizationEmbed">
      {!hideHeader && (
        <VisualizationEmbedHeader
          queryName={query.name}
          queryDescription={showQueryDescription ? query.description : null}
          visualization={visualization}
        />
      )}
      <div className="col-md-12 query__vis">
        {!hideParametersUI && query.hasParameters() && (
          <div className="p-t-15 p-b-10">
            <Parameters parameters={query.getParametersDefs()} onValuesChange={refreshQueryResults} />
          </div>
        )}
        {error && <div className="alert alert-danger" data-test="ErrorMessage">{`Error: ${error}`}</div>}
        {!error && queryResults && (
          <VisualizationRenderer visualization={visualization} queryResult={queryResults} context="widget" />
        )}
        {!queryResults && refreshStartedAt && (
          <div className="d-flex justify-content-center">
            <div className="spinner">
              <i className="zmdi zmdi-refresh zmdi-hc-spin zmdi-hc-5x" />
            </div>
          </div>
        )}
      </div>
      <VisualizationEmbedFooter
        query={query}
        queryResults={queryResults}
        updatedAt={queryResults ? queryResults.getUpdatedAt() : undefined}
        refreshStartedAt={refreshStartedAt}
        queryUrl={!hideQueryLink ? query.getUrl() : null}
      />
    </div>
  );
}

VisualizationEmbed.propTypes = { query: PropTypes.object.isRequired }; // eslint-disable-line react/forbid-prop-types

export default function init(ngModule) {
  ngModule.component("visualizationEmbed", react2angular(VisualizationEmbed));

  function loadSession($route, Auth) {
    const apiKey = $route.current.params.api_key;
    Auth.setApiKey(apiKey);
    return Auth.loadConfig();
  }

  function loadQuery($route, Auth, Query) {
    "ngInject";

    return loadSession($route, Auth).then(() => Query.get({ id: $route.current.params.queryId }).$promise);
  }

  ngModule.config($routeProvider => {
    $routeProvider.when("/embed/query/:queryId/visualization/:visualizationId", {
      resolve: {
        query: loadQuery,
      },
      reloadOnSearch: false,
      template: '<visualization-embed query="$resolve.query"></visualization-embed>',
    });<commit_msg>Viz Embed: Add option to hide timestamp (#4491)<commit_after>import React, { useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types";
import { find, has } from "lodash";
import { react2angular } from "react2angular";
import moment from "moment";
import { markdown } from "markdown";
import Button from "antd/lib/button";
import Dropdown from "antd/lib/dropdown";
import Icon from "antd/lib/icon";
import Menu from "antd/lib/menu";
import Tooltip from "antd/lib/tooltip";
import { $location, $routeParams } from "@/services/ng";
import { formatDateTime } from "@/filters/datetime";
import HtmlContent from "@/components/HtmlContent";
import { Parameters } from "@/components/Parameters";
import { Moment } from "@/components/proptypes";
import { TimeAgo } from "@/components/TimeAgo";
import { Timer } from "@/components/Timer";
import QueryResultsLink from "@/components/EditVisualizationButton/QueryResultsLink";
import VisualizationName from "@/visualizations/VisualizationName";
import { VisualizationRenderer } from "@/visualizations/VisualizationRenderer";
import { VisualizationType } from "@/visualizations";

import logoUrl from "@/assets/images/redash_icon_small.png";

function VisualizationEmbedHeader({ queryName, queryDescription, visualization }) {
  return (
    <div className="embed-heading p-b-10 p-r-15 p-l-15">
      <h3>
        <img src={logoUrl} alt="Redash Logo" style={{ height: "24px", verticalAlign: "text-bottom" }} />
        <VisualizationName visualization={visualization} /> {queryName}
        {queryDescription && (
          <small>
            <HtmlContent className="markdown text-muted">{markdown.toHTML(queryDescription || "")}</HtmlContent>
          </small>
        )}
      </h3>
    </div>
  );
}

VisualizationEmbedHeader.propTypes = {
  queryName: PropTypes.string.isRequired,
  queryDescription: PropTypes.string,
  visualization: VisualizationType.isRequired,
};

VisualizationEmbedHeader.defaultProps = { queryDescription: "" };

function VisualizationEmbedFooter({ query, queryResults, updatedAt, refreshStartedAt, queryUrl, hideTimestamp }) {
  const downloadMenu = (
    <Menu>
      <Menu.Item>
        <QueryResultsLink
          fileType="csv"
          query={query}
          queryResult={queryResults}
          apiKey={$routeParams.api_key}
          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}
          embed>
          <Icon type="file" /> Download as CSV File
        </QueryResultsLink>
      </Menu.Item>
      <Menu.Item>
        <QueryResultsLink
          fileType="tsv"
          query={query}
          queryResult={queryResults}
          apiKey={$routeParams.api_key}
          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}
          embed>
          <Icon type="file" /> Download as TSV File
        </QueryResultsLink>
      </Menu.Item>
      <Menu.Item>
        <QueryResultsLink
          fileType="xlsx"
          query={query}
          queryResult={queryResults}
          apiKey={$routeParams.api_key}
          disabled={!queryResults || !queryResults.getData || !queryResults.getData()}
          embed>
          <Icon type="file-excel" /> Download as Excel File
        </QueryResultsLink>
      </Menu.Item>
    </Menu>
  );

  return (
    <div className="tile__bottom-control">
      {!hideTimestamp && (
        <span>
          <a className="small hidden-print">
            <i className="zmdi zmdi-time-restore" />{" "}
            {refreshStartedAt ? <Timer from={refreshStartedAt} /> : <TimeAgo date={updatedAt} />}
          </a>
          <span className="small visible-print">
            <i className="zmdi zmdi-time-restore" /> {formatDateTime(updatedAt)}
          </span>
        </span>
      )}
      {queryUrl && (
        <span className="hidden-print">
          <Tooltip title="Open in Redash">
            <Button className="icon-button" href={queryUrl} target="_blank">
              <i className="fa fa-external-link" />
            </Button>
          </Tooltip>
          {!query.hasParameters() && (
            <Dropdown overlay={downloadMenu} disabled={!queryResults} trigger={["click"]} placement="topLeft">
              <Button loading={!queryResults && !!refreshStartedAt} className="m-l-5">
                Download Dataset
                <i className="fa fa-caret-up m-l-5" />
              </Button>
            </Dropdown>
          )}
        </span>
      )}
    </div>
  );
}

VisualizationEmbedFooter.propTypes = {
  query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  queryResults: PropTypes.object, // eslint-disable-line react/forbid-prop-types
  updatedAt: PropTypes.string,
  refreshStartedAt: Moment,
  queryUrl: PropTypes.string,
  hideTimestamp: PropTypes.bool,
};

VisualizationEmbedFooter.defaultProps = {
  queryResults: null,
  updatedAt: null,
  refreshStartedAt: null,
  queryUrl: null,
  hideTimestamp: false,
};

function VisualizationEmbed({ query }) {
  const [error, setError] = useState(null);
  const [refreshStartedAt, setRefreshStartedAt] = useState(null);
  const [queryResults, setQueryResults] = useState(null);
  const hideHeader = has($location.search(), "hide_header");
  const hideParametersUI = has($location.search(), "hide_parameters");
  const hideQueryLink = has($location.search(), "hide_link");
  const hideTimestamp = has($location.search(), "hide_timestamp");

  const showQueryDescription = has($location.search(), "showDescription");
  const visualizationId = parseInt($routeParams.visualizationId, 10);
  const visualization = find(query.visualizations, vis => vis.id === visualizationId);

  const refreshQueryResults = useCallback(() => {
    setError(null);
    setRefreshStartedAt(moment());
    query
      .getQueryResultPromise()
      .then(result => {
        setQueryResults(result);
      })
      .catch(err => {
        setError(err.getError());
      })
      .finally(() => setRefreshStartedAt(null));
  }, [query]);

  useEffect(() => {
    document.querySelector("body").classList.add("headless");
    refreshQueryResults();
  }, [refreshQueryResults]);

  return (
    <div className="tile m-l-10 m-r-10 p-t-10 embed__vis" data-test="VisualizationEmbed">
      {!hideHeader && (
        <VisualizationEmbedHeader
          queryName={query.name}
          queryDescription={showQueryDescription ? query.description : null}
          visualization={visualization}
        />
      )}
      <div className="col-md-12 query__vis">
        {!hideParametersUI && query.hasParameters() && (
          <div className="p-t-15 p-b-10">
            <Parameters parameters={query.getParametersDefs()} onValuesChange={refreshQueryResults} />
          </div>
        )}
        {error && <div className="alert alert-danger" data-test="ErrorMessage">{`Error: ${error}`}</div>}
        {!error && queryResults && (
          <VisualizationRenderer visualization={visualization} queryResult={queryResults} context="widget" />
        )}
        {!queryResults && refreshStartedAt && (
          <div className="d-flex justify-content-center">
            <div className="spinner">
              <i className="zmdi zmdi-refresh zmdi-hc-spin zmdi-hc-5x" />
            </div>
          </div>
        )}
      </div>
      <VisualizationEmbedFooter
        query={query}
        queryResults={queryResults}
        updatedAt={queryResults ? queryResults.getUpdatedAt() : undefined}
        refreshStartedAt={refreshStartedAt}
        queryUrl={!hideQueryLink ? query.getUrl() : null}
        hideTimestamp={hideTimestamp}
      />
    </div>
  );
}

VisualizationEmbed.propTypes = { query: PropTypes.object.isRequired }; // eslint-disable-line react/forbid-prop-types

export default function init(ngModule) {
  ngModule.component("visualizationEmbed", react2angular(VisualizationEmbed));

  function loadSession($route, Auth) {
    const apiKey = $route.current.params.api_key;
    Auth.setApiKey(apiKey);
    return Auth.loadConfig();
  }

  function loadQuery($route, Auth, Query) {
    "ngInject";

    return loadSession($route, Auth).then(() => Query.get({ id: $route.current.params.queryId }).$promise);
  }

  ngModule.config($routeProvider => {
    $routeProvider.when("/embed/query/:queryId/visualization/:visualizationId", {
      resolve: {
        query: loadQuery,
      },
      reloadOnSearch: false,
      template: '<visualization-embed query="$resolve.query"></visualization-embed>',
    });
{'fraction_non_alphanumeric': 0.14004180352343984, 'fraction_numerical': 0.0038220364287847117, 'mean_word_length': 3.091375519179086, 'pattern_counts': {'":': 0, '<': 143, '<?xml version=': 0, '>': 159, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3013529', 'n_tokens_mistral': 4841, 'n_tokens_neox': 4750, 'n_words': 928}