<commit_before>import _ from 'lodash';

import { renderUrl } from 'app/core/utils/url';
import kbn from 'app/core/utils/kbn';
import store from 'app/core/store';
import colors from 'app/core/utils/colors';
import { parse as parseDate } from 'app/core/utils/datemath';

import TimeSeries from 'app/core/time_series2';
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
import { ExploreState, ExploreUrlState, HistoryItem, QueryTransaction } from 'app/types/explore';
import { DataQuery, RawTimeRange, IntervalValues, DataSourceApi } from 'app/types/series';

export const DEFAULT_RANGE = {
  from: 'now-6h',
  to: 'now',
};

const MAX_HISTORY_ITEMS = 100;

/**
 * Returns an Explore-URL that contains a panel's queries and the dashboard time range.
 *
 * @param panel Origin panel of the jump to Explore
 * @param panelTargets The origin panel's query targets
 * @param panelDatasource The origin panel's datasource
 * @param datasourceSrv Datasource service to query other datasources in case the panel datasource is mixed
 * @param timeSrv Time service to get the current dashboard range from
 */
export async function getExploreUrl(
  panel: any,
  panelTargets: any[],
  panelDatasource: any,
  datasourceSrv: any,
  timeSrv: any
) {
  let exploreDatasource = panelDatasource;
  let exploreTargets: DataQuery[] = panelTargets;
  let url;

  // Mixed datasources need to choose only one datasource
  if (panelDatasource.meta.id === 'mixed' && panelTargets) {
    // Find first explore datasource among targets
    let mixedExploreDatasource;
    for (const t of panel.targets) {
      const datasource = await datasourceSrv.get(t.datasource);
      if (datasource && datasource.meta.explore) {
        mixedExploreDatasource = datasource;
        break;
      }
    }

    // Add all its targets
    if (mixedExploreDatasource) {
      exploreDatasource = mixedExploreDatasource;
      exploreTargets = panelTargets.filter(t => t.datasource === mixedExploreDatasource.name);
    }
  }

  if (panelDatasource) {
    const range = timeSrv.timeRangeForUrl();
    let state: Partial<ExploreUrlState> = { range };
    if (exploreDatasource.getExploreState) {
      state = { ...state, ...exploreDatasource.getExploreState(exploreTargets) };
    } else {
      state = {
        ...state,
        datasource: panelDatasource.name,
        queries: exploreTargets.map(t => ({ ...t, datasource: panelDatasource.name })),
      };
    }

    const exploreState = JSON.stringify(state);
    url = renderUrl('/explore', { state: exploreState });
  }
  return url;
}

const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;

export function parseUrlState(initial: string | undefined): ExploreUrlState {
  if (initial) {
    try {
      const parsed = JSON.parse(decodeURI(initial));
      if (Array.isArray(parsed)) {
        if (parsed.length <= 3) {
          throw new Error('Error parsing compact URL state for Explore.');
        }
        const range = {
          from: parsed[0],
          to: parsed[1],
        };
        const datasource = parsed[2];
        const queries = parsed.slice(3);
        return { datasource, queries, range };
      }
      return parsed;
    } catch (e) {
      console.error(e);
    }
  }
  return { datasource: null, queries: [], range: DEFAULT_RANGE };
}

export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string {
  const urlState: ExploreUrlState = {
    datasource: state.datasourceName,
    queries: state.initialQueries.map(clearQueryKeys),
    range: state.range,
  };
  if (compact) {
    return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]);
  }
  return JSON.stringify(urlState);
}

export function generateKey(index = 0): string {
  return `Q-${Date.now()}-${Math.random()}-${index}`;
}

export function generateRefId(index = 0): string {
  return `${index + 1}`;
}

export function generateQueryKeys(index = 0): { refId: string; key: string } {
  return { refId: generateRefId(index), key: generateKey(index) };
}

/**
 * Ensure at least one target exists and that targets have the necessary keys
 */
export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
  if (queries && typeof queries === 'object' && queries.length > 0) {
    return queries.map((query, i) => ({ ...query, ...generateQueryKeys(i) }));
  }
  return [{ ...generateQueryKeys() }];
}

/**
 * A target is non-empty when it has keys (with non-empty values) other than refId and key.
 */
export function hasNonEmptyQuery(queries: DataQuery[]): boolean {
  return queries.some(
    query =>
      Object.keys(query)
        .map(k => query[k])
        .filter(v => v).length > 2
  );
}

export function calculateResultsFromQueryTransactions(
  queryTransactions: QueryTransaction[],
  datasource: any,
  graphInterval: number
) {
  const graphResult = _.flatten(
    queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
  );
  const tableResult = mergeTablesIntoModel(
    new TableModel(),
    ...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done && qt.result).map(qt => qt.result)
  );
  const logsResult =
    datasource && datasource.mergeStreams
      ? datasource.mergeStreams(
          _.flatten(
            queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
          ),
          graphInterval
        )
      : undefined;

  return {
    graphResult,
    tableResult,
    logsResult,
  };
}<commit_msg>filter out table responses that don't have columns and rows<commit_after>import _ from 'lodash';

import { renderUrl } from 'app/core/utils/url';
import kbn from 'app/core/utils/kbn';
import store from 'app/core/store';
import colors from 'app/core/utils/colors';
import { parse as parseDate } from 'app/core/utils/datemath';

import TimeSeries from 'app/core/time_series2';
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
import { ExploreState, ExploreUrlState, HistoryItem, QueryTransaction } from 'app/types/explore';
import { DataQuery, RawTimeRange, IntervalValues, DataSourceApi } from 'app/types/series';

export const DEFAULT_RANGE = {
  from: 'now-6h',
  to: 'now',
};

const MAX_HISTORY_ITEMS = 100;

/**
 * Returns an Explore-URL that contains a panel's queries and the dashboard time range.
 *
 * @param panel Origin panel of the jump to Explore
 * @param panelTargets The origin panel's query targets
 * @param panelDatasource The origin panel's datasource
 * @param datasourceSrv Datasource service to query other datasources in case the panel datasource is mixed
 * @param timeSrv Time service to get the current dashboard range from
 */
export async function getExploreUrl(
  panel: any,
  panelTargets: any[],
  panelDatasource: any,
  datasourceSrv: any,
  timeSrv: any
) {
  let exploreDatasource = panelDatasource;
  let exploreTargets: DataQuery[] = panelTargets;
  let url;

  // Mixed datasources need to choose only one datasource
  if (panelDatasource.meta.id === 'mixed' && panelTargets) {
    // Find first explore datasource among targets
    let mixedExploreDatasource;
    for (const t of panel.targets) {
      const datasource = await datasourceSrv.get(t.datasource);
      if (datasource && datasource.meta.explore) {
        mixedExploreDatasource = datasource;
        break;
      }
    }

    // Add all its targets
    if (mixedExploreDatasource) {
      exploreDatasource = mixedExploreDatasource;
      exploreTargets = panelTargets.filter(t => t.datasource === mixedExploreDatasource.name);
    }
  }

  if (panelDatasource) {
    const range = timeSrv.timeRangeForUrl();
    let state: Partial<ExploreUrlState> = { range };
    if (exploreDatasource.getExploreState) {
      state = { ...state, ...exploreDatasource.getExploreState(exploreTargets) };
    } else {
      state = {
        ...state,
        datasource: panelDatasource.name,
        queries: exploreTargets.map(t => ({ ...t, datasource: panelDatasource.name })),
      };
    }

    const exploreState = JSON.stringify(state);
    url = renderUrl('/explore', { state: exploreState });
  }
  return url;
}

const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;

export function parseUrlState(initial: string | undefined): ExploreUrlState {
  if (initial) {
    try {
      const parsed = JSON.parse(decodeURI(initial));
      if (Array.isArray(parsed)) {
        if (parsed.length <= 3) {
          throw new Error('Error parsing compact URL state for Explore.');
        }
        const range = {
          from: parsed[0],
          to: parsed[1],
        };
        const datasource = parsed[2];
        const queries = parsed.slice(3);
        return { datasource, queries, range };
      }
      return parsed;
    } catch (e) {
      console.error(e);
    }
  }
  return { datasource: null, queries: [], range: DEFAULT_RANGE };
}

export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string {
  const urlState: ExploreUrlState = {
    datasource: state.datasourceName,
    queries: state.initialQueries.map(clearQueryKeys),
    range: state.range,
  };
  if (compact) {
    return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]);
  }
  return JSON.stringify(urlState);
}

export function generateKey(index = 0): string {
  return `Q-${Date.now()}-${Math.random()}-${index}`;
}

export function generateRefId(index = 0): string {
  return `${index + 1}`;
}

export function generateQueryKeys(index = 0): { refId: string; key: string } {
  return { refId: generateRefId(index), key: generateKey(index) };
}

/**
 * Ensure at least one target exists and that targets have the necessary keys
 */
export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
  if (queries && typeof queries === 'object' && queries.length > 0) {
    return queries.map((query, i) => ({ ...query, ...generateQueryKeys(i) }));
  }
  return [{ ...generateQueryKeys() }];
}

/**
 * A target is non-empty when it has keys (with non-empty values) other than refId and key.
 */
export function hasNonEmptyQuery(queries: DataQuery[]): boolean {
  return queries.some(
    query =>
      Object.keys(query)
        .map(k => query[k])
        .filter(v => v).length > 2
  );
}

export function calculateResultsFromQueryTransactions(
  queryTransactions: QueryTransaction[],
  datasource: any,
  graphInterval: number
) {
  const graphResult = _.flatten(
    queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
  );
  const tableResult = mergeTablesIntoModel(
    new TableModel(),
    ...queryTransactions
      .filter(qt => qt.resultType === 'Table' && qt.done && qt.result && qt.result.columns && qt.result.rows)
      .map(qt => qt.result)
  );
  const logsResult =
    datasource && datasource.mergeStreams
      ? datasource.mergeStreams(
          _.flatten(
            queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
          ),
          graphInterval
        )
      : undefined;

  return {
    graphResult,
    tableResult,
    logsResult,
  };
}
{'fraction_non_alphanumeric': 0.12731460972800568, 'fraction_numerical': 0.002835120049614601, 'mean_word_length': 3.3315425940138144, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '950382', 'n_tokens_mistral': 3551, 'n_tokens_neox': 3456, 'n_words': 905}