diff --git a/FN_README.md b/FN_README.md index 01e43147d5ac7..a5664f6638f6b 100644 --- a/FN_README.md +++ b/FN_README.md @@ -1,6 +1,4 @@ -# Sankey plugin - -## How to work with it? +## Sankey Plugin We created our fork of [snakey-panel](https://github.com/IsmaelMasharo/sankey-panel) ---> [our fork](https://github.com/fluxninja/sankey-panel) and if we want to add some changes to it we need to: @@ -10,19 +8,34 @@ We created our fork of [snakey-panel](https://github.com/IsmaelMasharo/sankey-pa - copy-paste builded version of plugin into grafana `public/app/plugins/panel/sankey-panel-0.5.0` - refresh grafana -# [Grafadruid-druid datasource plugin](./public/app/plugins/datasource//grafadruid-druid-datasource/) +## [Grafadruid-druid datasource plugin](./public/app/plugins/datasource//grafadruid-druid-datasource/) https://github.com/grafadruid/druid-grafana/tree/master/src -# [Graphql datasource plugin](./public/app/plugins//datasource//fifemon-graphql-datasource/) +## Local Testing + +Run Grafana locally using: + +```shell +make run + +yarn start +``` + +Port forward local prometheus and druid from [cloud](https://github.com/fluxninja/cloud) repo using fn: + +```shell +cd cloud + +LOGURU_LEVEL=TRACE fn dev service-proxy --cluster prometheus-cloud druid-router -https://github.com/fifemon/graphql-datasource/tree/v1.3.0/src +``` -Release: v1.3.0 +Connect to druid and prometheus on locally running Grafana UI. For this go to connection tab and select Data Source. Add new data source and make a connection. To test micro frontend make a connection with UI follow this [MFE Grafana](https://github.com/fluxninja/cloud/tree/main/services/cloud/ui#running-grafana-locally-for-development). -# Troubleshooting +## Troubleshooting -## "Cannot find module" typescript errors (ts2307) +### "Cannot find module" typescript errors (ts2307) Smart IDEs (such as VSCode or IntelliJ) require special configuration for TypeScript to work when using Plug'n'Play installs. A collection of settings for each editor can be found under the (link)[https://yarnpkg.com/getting-started/editor-sdks#vscode] diff --git a/public/app/core/reducers/fn-slice.ts b/public/app/core/reducers/fn-slice.ts index e518ca872fbbe..a710dc5c714a1 100644 --- a/public/app/core/reducers/fn-slice.ts +++ b/public/app/core/reducers/fn-slice.ts @@ -23,7 +23,7 @@ export type SetFnStateAction = PayloadAction; export type FnStateProp = keyof FnGlobalState; diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 509e32d6c1471..62bca32696825 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -1,4 +1,5 @@ import { cx } from '@emotion/css'; +import { Portal } from '@mui/material'; import React, { PureComponent } from 'react'; import { connect, ConnectedProps, MapDispatchToProps, MapStateToProps } from 'react-redux'; @@ -71,7 +72,7 @@ export type MapStateToDashboardPageProps = MapStateToProps< Pick & { dashboard: ReturnType; navIndex: StoreState['navIndex']; - } & Pick, + } & Pick, OwnProps, StoreState >; @@ -92,6 +93,7 @@ export const mapStateToProps: MapStateToDashboardPageProps = (state) => ({ dashboard: state.dashboard.getModel(), navIndex: state.navIndex, FNDashboard: state.fnGlobalState.FNDashboard, + controlsContainer: state.fnGlobalState.controlsContainer, }); const mapDispatchToProps: MapDispatchToDashboardPageProps = { @@ -376,7 +378,7 @@ export class UnthemedDashboardPage extends PureComponent { } render() { - const { dashboard, initError, queryParams, FNDashboard } = this.props; + const { dashboard, initError, queryParams, FNDashboard, controlsContainer } = this.props; const { editPanel, viewPanel, updateScrollTop, pageNav, sectionNav } = this.state; const kioskMode = getKioskMode(this.props.queryParams); @@ -402,6 +404,18 @@ export class UnthemedDashboardPage extends PureComponent { ); } + const FNTimeRange = !controlsContainer ? ( + + + + ) : ( + + + + + + ); + return ( { {showToolbar && (
{FNDashboard ? ( - - - + FNTimeRange ) : ( await import(/* webpackChunkName: "postgresPlugin" */ 'app/plugins/datasource/postgres/module'); const grafadruidDruidDatasourcePlugin = async () => await import(/* webpackChunkName: "druidPlugin" */ 'app/plugins/datasource/grafadruid-druid-datasource/module'); -const fifemonGraphqlDatasourcePlugin = async () => - await import(/* webpackChunkName: "graphqlPlugin" */ 'app/plugins/datasource/fifemon-graphql-datasource/module'); const prometheusPlugin = async () => await import(/* webpackChunkName: "prometheusPlugin" */ 'app/plugins/datasource/prometheus/module'); const mssqlPlugin = async () => @@ -102,8 +100,8 @@ const builtInPlugins: any = { 'app/plugins/datasource/mysql/module': mysqlPlugin, 'app/plugins/datasource/postgres/module': postgresPlugin, 'app/plugins/datasource/mssql/module': mssqlPlugin, + // Default FluxNinja Druid datasource 'app/plugins/datasource/grafadruid-druid-datasource/module': grafadruidDruidDatasourcePlugin, - 'app/plugins/datasource/fifemon-graphql-datasource/module': fifemonGraphqlDatasourcePlugin, 'app/plugins/datasource/prometheus/module': prometheusPlugin, 'app/plugins/datasource/testdata/module': testDataDSPlugin, 'app/plugins/datasource/cloud-monitoring/module': cloudMonitoringPlugin, diff --git a/public/app/fn-app/create-mfe.ts b/public/app/fn-app/create-mfe.ts index d87a26b1818f0..1741114d0fc64 100644 --- a/public/app/fn-app/create-mfe.ts +++ b/public/app/fn-app/create-mfe.ts @@ -4,7 +4,7 @@ declare let __webpack_public_path__: string; window.__grafana_public_path__ = __webpack_public_path__.substring(0, __webpack_public_path__.lastIndexOf('build/')) || __webpack_public_path__; -import { isNull, merge, noop, omit, pick } from 'lodash'; +import { isNull, merge, noop, pick } from 'lodash'; import React, { ComponentType } from 'react'; import ReactDOM from 'react-dom'; @@ -294,6 +294,7 @@ class createMfe { slug: other.slug, version: other.version, queryParams: other.queryParams, + controlsContainer: other.controlsContainer, }) ); } @@ -308,15 +309,10 @@ class createMfe { static renderMfeComponent(props: FNDashboardProps, onSuccess = noop) { const container = createMfe.getContainer(props); - ReactDOM.render( - // @ts-ignore - React.createElement(createMfe.Component, omit(props, 'hiddenVariables', 'FNDashboard')), - container, - () => { - createMfe.logger('Created mfe component.', { props, container }); - onSuccess(); - } - ); + ReactDOM.render(React.createElement(createMfe.Component, props), container, () => { + createMfe.logger('Created mfe component.', { props, container }); + onSuccess(); + }); } } diff --git a/public/app/fn-app/fn-dashboard-page/fn-dashboard.tsx b/public/app/fn-app/fn-dashboard-page/fn-dashboard.tsx index 1fc6aa5382d89..bacc95e98ec5b 100644 --- a/public/app/fn-app/fn-dashboard-page/fn-dashboard.tsx +++ b/public/app/fn-app/fn-dashboard-page/fn-dashboard.tsx @@ -1,14 +1,8 @@ import { pick } from 'lodash'; import React, { FC, useMemo } from 'react'; -import { connect, MapStateToProps } from 'react-redux'; -import { - FnGlobalState, - FN_STATE_KEY, - FnPropMappedFromState, - fnPropsMappedFromState, - FnPropsMappedFromState, -} from 'app/core/reducers/fn-slice'; +import { FnPropMappedFromState, fnPropsMappedFromState } from 'app/core/reducers/fn-slice'; +import { StoreState, useSelector } from 'app/types'; import { AngularRoot } from '../../angular/AngularRoot'; import { FnAppProvider } from '../fn-app-provider'; @@ -30,23 +24,29 @@ export const FNDashboard: FC = (props) => { ); }; -function mapStateToProps(): MapStateToProps< - FnPropsMappedFromState, - Omit, - { [K in typeof FN_STATE_KEY]: FnGlobalState } -> { - return ({ fnGlobalState }) => pick(fnGlobalState, ...fnPropsMappedFromState); +function mapStateToProps() { + return ({ fnGlobalState }: StoreState) => pick(fnGlobalState, ...fnPropsMappedFromState); } -export const DashboardPortalComponent: FC = (props) => { +export const DashboardPortal: FC = (p) => { + const globalFnProps = useSelector(mapStateToProps()); + + const props = useMemo( + () => ({ + ...p, + ...globalFnProps, + }), + [p, globalFnProps] + ); + const content = useMemo(() => { if (!props.FNDashboard) { return null; } - const { uid, slug, queryParams = {} } = props; + const { uid, queryParams = {} } = props; - if (!uid || !slug) { + if (!uid) { return null; } @@ -55,7 +55,6 @@ export const DashboardPortalComponent: FC @@ -64,5 +63,3 @@ export const DashboardPortalComponent: FC{content}; }; - -export const DashboardPortal = connect(mapStateToProps())(DashboardPortalComponent); diff --git a/public/app/fn-app/types.ts b/public/app/fn-app/types.ts index 1f6a1ccd090b0..c1355e1602df5 100644 --- a/public/app/fn-app/types.ts +++ b/public/app/fn-app/types.ts @@ -27,7 +27,7 @@ export interface FNDashboardProps { queryParams: ParsedQuery; fnError?: ReactNode; pageTitle?: string; - controlsContainer: string; + controlsContainer: string | null; isLoading: (isLoading: boolean) => void; setErrors: (errors?: { [K: number | string]: string }) => void; hiddenVariables: readonly string[]; diff --git a/public/app/fn-app/utils.tsx b/public/app/fn-app/utils.tsx index 5acdf0deee7ec..b4603a235c9cc 100644 --- a/public/app/fn-app/utils.tsx +++ b/public/app/fn-app/utils.tsx @@ -1,18 +1,16 @@ -import { FC, PropsWithChildren } from 'react'; -import ReactDOM from 'react-dom'; +import { Portal } from '@mui/material'; +import React, { FC, PropsWithChildren } from 'react'; export interface RenderPortalProps { ID: string; } -export const getPortalContainer = (ID: string): HTMLElement | null => document.getElementById(ID); - export const RenderPortal: FC> = ({ ID, children }) => { - const portalDiv = getPortalContainer(ID); + const container = document.getElementById(ID); - if (!portalDiv) { + if (!container) { return null; } - return ReactDOM.createPortal(children, portalDiv); + return {children}; }; diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/ConfigEditor.tsx b/public/app/plugins/datasource/fifemon-graphql-datasource/ConfigEditor.tsx deleted file mode 100644 index 7ee2121dc3bfb..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/ConfigEditor.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -import { DataSourcePluginOptionsEditorProps } from '@grafana/data'; -import { DataSourceHttpSettings } from '@grafana/ui'; - -import { MyDataSourceOptions } from './types'; - -export type Props = DataSourcePluginOptionsEditorProps; -export const ConfigEditor = (props: Props) => { - const { options, onOptionsChange } = props; - - return ( - <> - - - ); -}; diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/DataSource.ts b/public/app/plugins/datasource/fifemon-graphql-datasource/DataSource.ts deleted file mode 100644 index 881f384530c75..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/DataSource.ts +++ /dev/null @@ -1,360 +0,0 @@ -import { defaults, isEqual, isNumber } from 'lodash'; - -import { - AnnotationEvent, - DataQueryRequest, - DataQueryResponse, - DataSourceApi, - MetricFindValue, - DataSourceInstanceSettings, - ScopedVars, - TimeRange, - dateTime, - MutableDataFrame, - FieldType, - DataFrame, -} from '@grafana/data'; -import { getTemplateSrv } from '@grafana/runtime'; - -import { - MyQuery, - MyDataSourceOptions, - defaultQuery, - MyVariableQuery, - MultiValueVariable, - TextValuePair, -} from './types'; -import { flatten, isRFC3339_ISO6801 } from './util'; - -const supportedVariableTypes = ['constant', 'custom', 'query', 'textbox']; - -export class DataSource extends DataSourceApi { - basicAuth: string | undefined; - withCredentials: boolean | undefined; - url: string | undefined; - - constructor( - instanceSettings: DataSourceInstanceSettings, - private backendSrv: any - ) { - super(instanceSettings); - this.basicAuth = instanceSettings.basicAuth; - this.withCredentials = instanceSettings.withCredentials; - this.url = instanceSettings.url; - } - - private request(data: string) { - const options: any = { - url: this.url, - method: 'POST', - data: { - query: data, - }, - }; - - if (this.basicAuth || this.withCredentials) { - options.withCredentials = true; - } - if (this.basicAuth) { - options.headers = { - Authorization: this.basicAuth, - }; - } - - return this.backendSrv.datasourceRequest(options); - } - - private postQuery(query: Partial, payload: string) { - return this.request(payload) - .then((results: any) => { - return { query, results }; - }) - .catch((err: any) => { - if (err.data && err.data.error) { - throw { - message: 'GraphQL error: ' + err.data.error.reason, - error: err.data.error, - }; - } - - throw err; - }); - } - - private createQuery(query: MyQuery, range: TimeRange | undefined, scopedVars: ScopedVars | undefined = undefined) { - let payload = getTemplateSrv().replace(query.queryText, { - ...scopedVars, - timeFrom: { text: 'from', value: range?.from.valueOf() }, - timeTo: { text: 'to', value: range?.to.valueOf() }, - }); - - //console.log(payload); - return this.postQuery(query, payload); - } - private static getDocs(resultsData: any, dataPath: string): any[] { - if (!resultsData) { - throw 'resultsData was null or undefined'; - } - let data = dataPath.split('.').reduce((d: any, p: any) => { - if (!d) { - return null; - } - return d[p]; - }, resultsData.data); - if (!data) { - const errors: any[] = resultsData.errors; - if (errors && errors.length !== 0) { - throw errors[0]; - } - throw 'Your data path did not exist! dataPath: ' + dataPath; - } - if (resultsData.errors) { - // There can still be errors even if there is data - console.log('Got GraphQL errors:'); - console.log(resultsData.errors); - } - const docs: any[] = []; - let pushDoc = (originalDoc: object) => { - docs.push(flatten(originalDoc)); - }; - if (Array.isArray(data)) { - for (const element of data) { - pushDoc(element); - } - } else { - pushDoc(data); - } - return docs; - } - private static getDataPathArray(dataPathString: string): string[] { - const dataPathArray: string[] = []; - for (const dataPath of dataPathString.split(',')) { - const trimmed = dataPath.trim(); - if (trimmed) { - dataPathArray.push(trimmed); - } - } - if (!dataPathArray) { - throw 'data path is empty!'; - } - return dataPathArray; - } - - async query(options: DataQueryRequest): Promise { - return Promise.all( - options.targets.map((target) => { - return this.createQuery(defaults(target, defaultQuery), options.range, options.scopedVars); - }) - ).then((results: any) => { - const dataFrameArray: DataFrame[] = []; - for (let res of results) { - const dataPathArray: string[] = DataSource.getDataPathArray(res.query.dataPath); - const { timePath, timeFormat, groupBy, aliasBy } = res.query; - const split = groupBy.split(','); - const groupByList: string[] = []; - for (const element of split) { - const trimmed = element.trim(); - if (trimmed) { - groupByList.push(trimmed); - } - } - for (const dataPath of dataPathArray) { - const docs: any[] = DataSource.getDocs(res.results.data, dataPath); - - const dataFrameMap = new Map(); - for (const doc of docs) { - if (timePath in doc) { - doc[timePath] = dateTime(doc[timePath], timeFormat); - } - const identifiers: string[] = []; - for (const groupByElement of groupByList) { - identifiers.push(doc[groupByElement]); - } - const identifiersString = identifiers.toString(); - let dataFrame = dataFrameMap.get(identifiersString); - if (!dataFrame) { - // we haven't initialized the dataFrame for this specific identifier that we group by yet - dataFrame = new MutableDataFrame({ fields: [] }); - const generalReplaceObject: any = {}; - for (const fieldName in doc) { - generalReplaceObject['field_' + fieldName] = doc[fieldName]; - } - for (const fieldName in doc) { - let t: FieldType = FieldType.string; - if (fieldName === timePath || isRFC3339_ISO6801(String(doc[fieldName]))) { - t = FieldType.time; - } else if (isNumber(doc[fieldName])) { - t = FieldType.number; - } - let title; - if (identifiers.length !== 0) { - // if we have any identifiers - title = identifiersString + '_' + fieldName; - } else { - title = fieldName; - } - if (aliasBy) { - title = aliasBy; - const replaceObject = { ...generalReplaceObject }; - replaceObject['fieldName'] = fieldName; - for (const replaceKey in replaceObject) { - const replaceValue = replaceObject[replaceKey]; - const regex = new RegExp('\\$' + replaceKey, 'g'); - title = title.replace(regex, replaceValue); - } - title = getTemplateSrv().replace(title, options.scopedVars); - } - dataFrame.addField({ - name: fieldName, - type: t, - config: { displayName: title }, - // @ts-ignore - }).parse = (v: any) => { - return v || ''; - }; - } - dataFrameMap.set(identifiersString, dataFrame); - } - - dataFrame.add(doc); - } - for (const dataFrame of dataFrameMap.values()) { - dataFrameArray.push(dataFrame); - } - } - } - return { data: dataFrameArray }; - }); - } - annotationQuery(options: any): Promise { - const query = defaults(options.annotation, defaultQuery); - return Promise.all([this.createQuery(query, options.range)]).then((results: any) => { - const r: AnnotationEvent[] = []; - for (const res of results) { - const { timePath, endTimePath, timeFormat } = res.query; - const dataPathArray: string[] = DataSource.getDataPathArray(res.query.dataPath); - for (const dataPath of dataPathArray) { - const docs: any[] = DataSource.getDocs(res.results.data, dataPath); - for (const doc of docs) { - const annotation: AnnotationEvent = {}; - if (timePath in doc) { - annotation.time = dateTime(doc[timePath], timeFormat).valueOf(); - } - if (endTimePath in doc) { - annotation.isRegion = true; - annotation.timeEnd = dateTime(doc[endTimePath], timeFormat).valueOf(); - } - let title = query.annotationTitle; - let text = query.annotationText; - let tags = query.annotationTags; - for (const fieldName in doc) { - const fieldValue = doc[fieldName]; - const replaceKey = 'field_' + fieldName; - const regex = new RegExp('\\$' + replaceKey, 'g'); - title = title.replace(regex, fieldValue); - text = text.replace(regex, fieldValue); - tags = tags.replace(regex, fieldValue); - } - - annotation.title = title; - annotation.text = text; - const tagsList: string[] = []; - for (const element of tags.split(',')) { - const trimmed = element.trim(); - if (trimmed) { - tagsList.push(trimmed); - } - } - annotation.tags = tagsList; - r.push(annotation); - } - } - } - return r; - }); - } - - testDatasource() { - const q = `{ - __schema{ - queryType{name} - } - }`; - return this.postQuery(defaultQuery, q).then( - (res: any) => { - if (res.errors) { - console.log(res.errors); - return { - status: 'error', - message: 'GraphQL Error: ' + res.errors[0].message, - }; - } - return { - status: 'success', - message: 'Success', - }; - }, - (err: any) => { - console.log(err); - return { - status: 'error', - message: 'HTTP Response ' + err.status + ': ' + err.statusText, - }; - } - ); - } - - async metricFindQuery(query: MyVariableQuery, options?: any) { - const metricFindValues: MetricFindValue[] = []; - - query = defaults(query, defaultQuery); - - let payload = query.queryText; - payload = getTemplateSrv().replace(payload, { ...this.getVariables }); - - const response = await this.postQuery(query, payload); - - const docs: any[] = DataSource.getDocs(response.results.data, query.dataPath); - - for (const doc of docs) { - if ('__text' in doc && '__value' in doc) { - metricFindValues.push({ text: doc['__text'], value: doc['__value'] }); - } else { - for (const fieldName in doc) { - metricFindValues.push({ text: doc[fieldName] }); - } - } - } - - return metricFindValues; - } - - getVariables() { - const variables: { [id: string]: TextValuePair } = {}; - Object.values(getTemplateSrv().getVariables()).forEach((variable) => { - if (!supportedVariableTypes.includes(variable.type)) { - console.warn(`Variable of type "${variable.type}" is not supported`); - - return; - } - - const supportedVariable = variable as MultiValueVariable; - - let variableValue = supportedVariable.current.value; - if (variableValue === '$__all' || isEqual(variableValue, ['$__all'])) { - if (supportedVariable.allValue === null || supportedVariable.allValue === '') { - variableValue = supportedVariable.options.slice(1).map((textValuePair) => textValuePair.value); - } else { - variableValue = supportedVariable.allValue; - } - } - - variables[supportedVariable.id] = { - text: supportedVariable.current.text, - value: variableValue, - }; - }); - - return variables; - } -} diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/FN_DataSource.ts b/public/app/plugins/datasource/fifemon-graphql-datasource/FN_DataSource.ts deleted file mode 100644 index 5554c3c41fa27..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/FN_DataSource.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { isEqual, defaults, isNumber } from 'lodash'; - -import { - AnnotationEvent, - AnnotationQueryRequest, - DataQueryRequest, - DataQueryResponse, - MetricFindValue, - DataSourceInstanceSettings, - ScopedVars, - TimeRange, - MutableDataFrame, - dateTime, - DataFrame, - FieldType, -} from '@grafana/data'; -import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime'; -import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv'; - -import { BackendSrvRequest } from '../../../../../packages/grafana-runtime/src/services/backendSrv'; - -import { - MyQuery, - MyDataSourceOptions, - defaultQuery, - MyVariableQuery, - MultiValueVariable, - TextValuePair, -} from './types'; -import { flatten, isRFC3339_ISO6801 } from './util'; - -const supportedVariableTypes = ['constant', 'custom', 'query', 'textbox']; - -export class FN_DataSource extends DataSourceWithBackend { - basicAuth: string | undefined; - withCredentials: boolean | undefined; - url: string | undefined; - backendSrv: BackendSrv; - - constructor(instanceSettings: DataSourceInstanceSettings) { - super(instanceSettings); - this.basicAuth = instanceSettings.basicAuth; - this.withCredentials = instanceSettings.withCredentials; - - console.log({ instanceSettings }); - // @ts-ignore - const url = instanceSettings.jsonData['connection.url']; - // const url = "http://localhost:8081/api/query" - this.url = url; - - this.backendSrv = getBackendSrv(); - } - - private request(data: string) { - const options: BackendSrvRequest = { - url: this.url as string, - method: 'POST', - data: { - query: data, - }, - }; - - if (this.basicAuth || this.withCredentials) { - options.withCredentials = true; - } - - if (this.basicAuth) { - options.headers = { - Authorization: this.basicAuth, - }; - } - - return this.backendSrv.datasourceRequest(options); - } - - private postQuery(query: Partial, payload: string) { - return this.request(payload) - .then((results: any) => { - return { query, results }; - }) - .catch((err: any) => { - if (err.data && err.data.error) { - throw { - message: 'GraphQL error: ' + err.data.error.reason, - error: err.data.error, - }; - } - - throw err; - }); - } - - private createQuery(query: MyQuery, range: TimeRange | undefined, scopedVars: ScopedVars | undefined = undefined) { - let payload = getTemplateSrv().replace(query.queryText, { - ...scopedVars, - timeFrom: { text: 'from', value: range?.from.valueOf() }, - timeTo: { text: 'to', value: range?.to.valueOf() }, - }); - - //console.log(payload); - return this.postQuery(query, payload); - } - private static getDocs(resultsData: any, dataPath: string): any[] { - if (!resultsData) { - throw 'resultsData was null or undefined'; - } - let data = dataPath.split('.').reduce((d: any, p: any) => { - if (!d) { - return null; - } - return d[p]; - }, resultsData.data); - if (!data) { - const errors: any[] = resultsData.errors; - if (errors && errors.length !== 0) { - throw errors[0]; - } - throw 'Your data path did not exist! dataPath: ' + dataPath; - } - if (resultsData.errors) { - // There can still be errors even if there is data - console.log('Got GraphQL errors:'); - console.log(resultsData.errors); - } - const docs: any[] = []; - let pushDoc = (originalDoc: object) => { - docs.push(flatten(originalDoc)); - }; - if (Array.isArray(data)) { - for (const element of data) { - pushDoc(element); - } - } else { - pushDoc(data); - } - return docs; - } - private static getDataPathArray(dataPathString: string): string[] { - const dataPathArray: string[] = []; - for (const dataPath of dataPathString.split(',')) { - const trimmed = dataPath.trim(); - if (trimmed) { - dataPathArray.push(trimmed); - } - } - if (!dataPathArray) { - throw 'data path is empty!'; - } - return dataPathArray; - } - - // @ts-ignore - async query(options: DataQueryRequest): Promise { - return Promise.all( - options.targets.map((target) => { - return this.createQuery(defaults(target, defaultQuery), options.range, options.scopedVars); - }) - ).then((results: any) => { - const dataFrameArray: DataFrame[] = []; - for (let res of results) { - const dataPathArray: string[] = FN_DataSource.getDataPathArray(res.query.dataPath); - const { timePath, timeFormat, groupBy, aliasBy } = res.query; - const split = groupBy.split(','); - const groupByList: string[] = []; - for (const element of split) { - const trimmed = element.trim(); - if (trimmed) { - groupByList.push(trimmed); - } - } - for (const dataPath of dataPathArray) { - const docs: any[] = FN_DataSource.getDocs(res.results.data, dataPath); - - const dataFrameMap = new Map(); - for (const doc of docs) { - if (timePath in doc) { - doc[timePath] = dateTime(doc[timePath], timeFormat); - } - const identifiers: string[] = []; - for (const groupByElement of groupByList) { - identifiers.push(doc[groupByElement]); - } - const identifiersString = identifiers.toString(); - let dataFrame = dataFrameMap.get(identifiersString); - if (!dataFrame) { - // we haven't initialized the dataFrame for this specific identifier that we group by yet - dataFrame = new MutableDataFrame({ fields: [] }); - const generalReplaceObject: any = {}; - for (const fieldName in doc) { - generalReplaceObject['field_' + fieldName] = doc[fieldName]; - } - for (const fieldName in doc) { - let t: FieldType = FieldType.string; - if (fieldName === timePath || isRFC3339_ISO6801(String(doc[fieldName]))) { - t = FieldType.time; - } else if (isNumber(doc[fieldName])) { - t = FieldType.number; - } - let title; - if (identifiers.length !== 0) { - // if we have any identifiers - title = identifiersString + '_' + fieldName; - } else { - title = fieldName; - } - if (aliasBy) { - title = aliasBy; - const replaceObject = { ...generalReplaceObject }; - replaceObject['fieldName'] = fieldName; - for (const replaceKey in replaceObject) { - const replaceValue = replaceObject[replaceKey]; - const regex = new RegExp('\\$' + replaceKey, 'g'); - title = title.replace(regex, replaceValue); - } - title = getTemplateSrv().replace(title, options.scopedVars); - } - dataFrame.addField({ - name: fieldName, - type: t, - config: { displayName: title }, - // @ts-ignore - }).parse = (v: any) => { - return v || ''; - }; - } - dataFrameMap.set(identifiersString, dataFrame); - } - - dataFrame.add(doc); - } - for (const dataFrame of dataFrameMap.values()) { - dataFrameArray.push(dataFrame); - } - } - } - return { data: dataFrameArray }; - }); - } - annotationQuery(options: AnnotationQueryRequest): Promise { - const query = defaults(options.annotation, defaultQuery); - // @ts-ignore - return Promise.all([this.createQuery(query, options.range)]).then((results: any) => { - const r: AnnotationEvent[] = []; - for (const res of results) { - const { timePath, endTimePath, timeFormat } = res.query; - const dataPathArray: string[] = FN_DataSource.getDataPathArray(res.query.dataPath); - for (const dataPath of dataPathArray) { - const docs: any[] = FN_DataSource.getDocs(res.results.data, dataPath); - for (const doc of docs) { - const annotation: AnnotationEvent = {}; - if (timePath in doc) { - annotation.time = dateTime(doc[timePath], timeFormat).valueOf(); - } - if (endTimePath in doc) { - annotation.isRegion = true; - annotation.timeEnd = dateTime(doc[endTimePath], timeFormat).valueOf(); - } - let title = query.annotationTitle; - let text = query.annotationText; - let tags = query.annotationTags; - for (const fieldName in doc) { - const fieldValue = doc[fieldName]; - const replaceKey = 'field_' + fieldName; - const regex = new RegExp('\\$' + replaceKey, 'g'); - title = title?.replace(regex, fieldValue); - text = text?.replace(regex, fieldValue); - tags = tags?.replace(regex, fieldValue); - } - - annotation.title = title; - annotation.text = text; - const tagsList: string[] = []; - for (const element of (tags || '').split(',')) { - const trimmed = element.trim(); - if (trimmed) { - tagsList.push(trimmed); - } - } - annotation.tags = tagsList; - r.push(annotation); - } - } - } - return r; - }); - } - - testDatasource() { - const q = `{ - __schema{ - queryType{name} - } - }`; - return this.postQuery(defaultQuery, q).then( - (res: any) => { - if (res.errors) { - console.log(res.errors); - return { - status: 'error', - message: 'GraphQL Error: ' + res.errors[0].message, - }; - } - return { - status: 'success', - message: 'Success', - }; - }, - (err: any) => { - console.log(err); - return { - status: 'error', - message: 'HTTP Response ' + err.status + ': ' + err.statusText, - }; - } - ); - } - - async metricFindQuery(query: MyVariableQuery, options?: any) { - const metricFindValues: MetricFindValue[] = []; - - query = defaults(query, defaultQuery); - - let payload = query.queryText; - payload = getTemplateSrv().replace(payload, { ...this.getVariables }); - - const response = await this.postQuery(query, payload); - - const docs: any[] = FN_DataSource.getDocs(response.results.data, query.dataPath); - - for (const doc of docs) { - if ('__text' in doc && '__value' in doc) { - metricFindValues.push({ text: doc['__text'], value: doc['__value'] }); - } else { - for (const fieldName in doc) { - metricFindValues.push({ text: doc[fieldName] }); - } - } - } - - return metricFindValues; - } - - getVariables() { - const variables: { [id: string]: TextValuePair } = {}; - Object.values(getTemplateSrv().getVariables()).forEach((variable) => { - if (!supportedVariableTypes.includes(variable.type)) { - console.warn(`Variable of type "${variable.type}" is not supported`); - - return; - } - - const supportedVariable = variable as MultiValueVariable; - - let variableValue = supportedVariable.current.value; - if (variableValue === '$__all' || isEqual(variableValue, ['$__all'])) { - if (supportedVariable.allValue === null || supportedVariable.allValue === '') { - variableValue = supportedVariable.options.slice(1).map((textValuePair) => textValuePair.value); - } else { - variableValue = supportedVariable.allValue; - } - } - - variables[supportedVariable.id] = { - text: supportedVariable.current.text, - value: variableValue, - }; - }); - - return variables; - } -} diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/GraphQLAnnotationsQueryCtrl.tsx b/public/app/plugins/datasource/fifemon-graphql-datasource/GraphQLAnnotationsQueryCtrl.tsx deleted file mode 100644 index cfc76866f2fe2..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/GraphQLAnnotationsQueryCtrl.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export class GraphQLAnnotationsQueryCtrl { - static templateUrl = 'partials/annotations.editor.html'; - annotation: any; - constructor() {} -} diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/QueryEditor.tsx b/public/app/plugins/datasource/fifemon-graphql-datasource/QueryEditor.tsx deleted file mode 100644 index e09777a1f7607..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/QueryEditor.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { defaults } from 'lodash'; -import React, { PureComponent, ChangeEvent } from 'react'; - -import { QueryEditorProps } from '@grafana/data'; -import { LegacyForms, QueryField, Icon } from '@grafana/ui'; - -import { FN_DataSource } from './FN_DataSource'; -import { MyQuery, MyDataSourceOptions, defaultQuery } from './types'; - -type Props = QueryEditorProps; - -interface State {} - -export class QueryEditor extends PureComponent { - onComponentDidMount() {} - - onChangeQuery = (value: string, override?: boolean) => { - const { onChange, query } = this.props; - if (onChange) { - onChange({ ...query, queryText: value }); - } - }; - - onDataPathTextChange = (event: ChangeEvent) => { - const { onChange, query } = this.props; - onChange({ ...query, dataPath: event.target.value }); - }; - onTimePathTextChange = (event: ChangeEvent) => { - const { onChange, query } = this.props; - onChange({ ...query, timePath: event.target.value }); - }; - onTimeFormatTextChange = (event: ChangeEvent) => { - const { onChange, query } = this.props; - onChange({ ...query, timeFormat: event.target.value }); - }; - onGroupByTextChange = (event: ChangeEvent) => { - const { onChange, query } = this.props; - onChange({ ...query, groupBy: event.target.value }); - }; - - onAliasByTextChange = (event: ChangeEvent) => { - const { onChange, query } = this.props; - onChange({ ...query, aliasBy: event.target.value }); - }; - - render() { - const query = defaults(this.props.query, defaultQuery); - const { queryText, dataPath, timePath, timeFormat, groupBy, aliasBy } = query; - - return ( - <> - -
- -
-
- -
-
- - Optional time format in moment.js format.  - - - } - /> -
-
- -
-
- -
- - ); - } -} diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/VariableQueryEditor.tsx b/public/app/plugins/datasource/fifemon-graphql-datasource/VariableQueryEditor.tsx deleted file mode 100644 index 9f475af300aa8..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/VariableQueryEditor.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useState } from 'react'; - -import { QueryField } from '@grafana/ui'; - -import { MyQuery } from './types'; - -interface VariableQueryProps { - query: MyQuery; - onChange: (query: MyQuery, definition: string) => void; -} - -export const VariableQueryEditor: React.FC = ({ onChange, query }) => { - const [state, setState] = useState(query); - - const saveQuery = () => { - onChange(state, `${state.queryText} (${state.dataPath})`); - }; - - const onChangeQuery = (value: string, override?: boolean) => - setState({ - ...state, - queryText: value, - }); - - const handleChange = (event: React.FormEvent) => - setState({ - ...state, - [event.currentTarget.name]: event.currentTarget.value, - }); - - return ( - <> -
- Data Path - -
-
- Query - -
- - ); -}; diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/img/README b/public/app/plugins/datasource/fifemon-graphql-datasource/img/README deleted file mode 100644 index 0b1525180d962..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/img/README +++ /dev/null @@ -1,4 +0,0 @@ -GraphQL Logo Copyright © 2015-2018, Facebook, Inc. Copyright © 2019-present, GraphQL Foundation -Obtained from: https://github.com/graphql/graphql-spec/blob/master/resources/GraphQL%20Logo.svg -Used under the terms of the Open Web Foundation Final Specification Agreement (OWFa 1.0) per -https://github.com/graphql/graphql-spec/blob/master/spec/GraphQL.md \ No newline at end of file diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/img/db_arrivals.png b/public/app/plugins/datasource/fifemon-graphql-datasource/img/db_arrivals.png deleted file mode 100644 index 5f9e3ce77d6c3..0000000000000 Binary files a/public/app/plugins/datasource/fifemon-graphql-datasource/img/db_arrivals.png and /dev/null differ diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/img/db_arrivals_annotations.png b/public/app/plugins/datasource/fifemon-graphql-datasource/img/db_arrivals_annotations.png deleted file mode 100644 index fd4d51ba8dbbc..0000000000000 Binary files a/public/app/plugins/datasource/fifemon-graphql-datasource/img/db_arrivals_annotations.png and /dev/null differ diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/img/github_security_advisories.png b/public/app/plugins/datasource/fifemon-graphql-datasource/img/github_security_advisories.png deleted file mode 100644 index 71300cfb7c371..0000000000000 Binary files a/public/app/plugins/datasource/fifemon-graphql-datasource/img/github_security_advisories.png and /dev/null differ diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/img/logo.svg b/public/app/plugins/datasource/fifemon-graphql-datasource/img/logo.svg deleted file mode 100644 index 58b2cc2877cdd..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/img/logo.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/module.test.ts b/public/app/plugins/datasource/fifemon-graphql-datasource/module.test.ts deleted file mode 100644 index 5e199ed0fa738..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/module.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe('placeholder test', () => { - it('should return true', () => { - expect(true).toBeTruthy(); - }); -}); diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/module.ts b/public/app/plugins/datasource/fifemon-graphql-datasource/module.ts deleted file mode 100644 index f06f7a876879c..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { DataSourcePlugin } from '@grafana/data'; - -import { ConfigEditor } from './ConfigEditor'; -import { FN_DataSource } from './FN_DataSource'; -import { GraphQLAnnotationsQueryCtrl } from './GraphQLAnnotationsQueryCtrl'; -import { QueryEditor } from './QueryEditor'; -import { VariableQueryEditor } from './VariableQueryEditor'; -import { MyQuery, MyDataSourceOptions } from './types'; - -export const plugin = new DataSourcePlugin(FN_DataSource) - .setConfigEditor(ConfigEditor) - .setAnnotationQueryCtrl(GraphQLAnnotationsQueryCtrl) - .setQueryEditor(QueryEditor) - .setVariableQueryEditor(VariableQueryEditor); diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/partials/annotations.editor.html b/public/app/plugins/datasource/fifemon-graphql-datasource/partials/annotations.editor.html deleted file mode 100644 index 99824c44211fe..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/partials/annotations.editor.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - -
-
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
-
- - diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/plugin.json b/public/app/plugins/datasource/fifemon-graphql-datasource/plugin.json deleted file mode 100644 index cf5a57a9fa2e3..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/plugin.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "type": "datasource", - "name": "GraphQL Data Source", - "id": "fifemon-graphql-datasource", - "metrics": true, - "annotations": true, - - "info": { - "description": "GraphQL Data Source", - "author": { - "name": "Kevin Retzke" - }, - "keywords": ["GraphQL"], - "logos": { - "small": "img/logo.svg", - "large": "img/logo.svg" - }, - "links": [ - { "name": "Website", "url": "https://github.com/fifemon/graphql-datasource" }, - { "name": "License", "url": "https://github.com/fifemon/graphql-datasource/blob/master/LICENSE" } - ], - "screenshots": [ - { "name": "DeutscheBahn Arrivals", "path": "img/db_arrivals.png"}, - { "name": "DeutscheBahn Arrivals Annotations", "path": "img/db_arrivals_annotations.png"}, - { "name": "GitHub Security Advisories", "path": "img/github_security_advisories.png"} - ], - "version": "%VERSION%", - "updated": "%TODAY%" - }, - - "dependencies": { - "grafanaVersion": "7.x.x", - "grafanaDependency": ">=7.0.0", - "plugins": [] - } -} diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/types.ts b/public/app/plugins/datasource/fifemon-graphql-datasource/types.ts deleted file mode 100644 index 4b4819512a3a2..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/types.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { DataQuery, DataSourceJsonData, VariableModel } from '@grafana/data'; - -export interface MyQuery extends DataQuery { - queryText: string; - dataPath: string; - timePath: string; - endTimePath: string | null; - timeFormat: string | null; - groupBy: string; - aliasBy: string; - annotationTitle: string; - annotationText: string; - annotationTags: string; - constant: number; -} - -export const defaultQuery: Partial = { - queryText: `query { - data:submissions(user:"$user"){ - Time:submitTime - idle running completed - } -}`, - dataPath: 'data', - timePath: 'Time', - endTimePath: 'endTime', - timeFormat: null, - groupBy: '', // `identifier` - aliasBy: '', // 'Server [[tag_identifier]]` - annotationTitle: '', - annotationText: '', - annotationTags: '', - constant: 6.5, -}; - -/** - * These are options configured for each DataSource instance - */ -export interface MyDataSourceOptions extends DataSourceJsonData { - apiKey?: string; -} - -export interface MyVariableQuery extends DataQuery { - dataPath: string; - queryText: string; -} - -export interface TextValuePair { - text: string; - value: any; -} - -export interface MultiValueVariable extends VariableModel { - allValue: string | null; - id: string; - current: TextValuePair; - options: TextValuePair[]; -} diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/util.test.ts b/public/app/plugins/datasource/fifemon-graphql-datasource/util.test.ts deleted file mode 100644 index c91386f359147..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/util.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { flatten, isRFC3339_ISO6801 } from './util'; - -test('flatten function test', () => { - let obj = { - string: 'hello', - number: 123, - float: 123.4, - null: null, - undefined: undefined, - array: [1, 2, 3], - nested: { - string: 'hello', - number: 123, - float: 123.4, - null: null, - undefined: undefined, - }, - }; - - let flattenObj = { - string: 'hello', - number: 123, - float: 123.4, - null: null, - undefined: undefined, - 'array.0': 1, - 'array.1': 2, - 'array.2': 3, - 'nested.string': 'hello', - 'nested.number': 123, - 'nested.float': 123.4, - 'nested.null': null, - 'nested.undefined': undefined, - }; - - expect(flatten(obj)).toEqual(flattenObj); -}); - -test('RFC3339 and ISO8601 valid string test', () => { - expect(isRFC3339_ISO6801('I am not a date but a string')).toBe(false); - expect(isRFC3339_ISO6801('1234')).toBe(false); - expect(isRFC3339_ISO6801(String(8))).toBe(false); - expect(isRFC3339_ISO6801(String(null))).toBe(false); - expect(isRFC3339_ISO6801('2020-06-01T00:00:00.000Z')).toBe(true); - expect(isRFC3339_ISO6801('2020-06-01T00:00:00Z')).toBe(true); - expect(isRFC3339_ISO6801(String(true))).toBe(false); - expect(isRFC3339_ISO6801(String(0))).toBe(false); - expect(isRFC3339_ISO6801(String(Number.MAX_SAFE_INTEGER))).toBe(false); - expect(isRFC3339_ISO6801(String(0.111111))).toBe(false); - expect(isRFC3339_ISO6801(String(1603810963000))).toBe(false); -}); diff --git a/public/app/plugins/datasource/fifemon-graphql-datasource/util.ts b/public/app/plugins/datasource/fifemon-graphql-datasource/util.ts deleted file mode 100644 index e3f586668514c..0000000000000 --- a/public/app/plugins/datasource/fifemon-graphql-datasource/util.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { dateTime, ISO_8601 } from '@grafana/data'; - -export function flatten>(object: T, path: string | null = null, separator = '.'): T { - return Object.keys(object).reduce((acc: T, key: string): T => { - const isObject = typeof object[key] === 'object' && object[key] != null; - const newPath = [path, key].filter(Boolean).join(separator); - return isObject ? { ...acc, ...flatten(object[key], newPath, separator) } : { ...acc, [newPath]: object[key] }; - }, {} as T); -} - -export function isRFC3339_ISO6801(str: string): boolean { - let date = dateTime(str, ISO_8601); - if (date.isValid()) { - let iso = date.toISOString(); - if (iso === str) { - return true; - } else { - // some RFC3339 dates don't include fractions of a second to same resolution, but still valid. - return iso.substring(0, 19) === str.substring(0, 19); - } - } - return false; -}