diff --git a/.github/workflows/bundle-types.yml b/.github/workflows/bundle-types.yml new file mode 100644 index 0000000..74d9db0 --- /dev/null +++ b/.github/workflows/bundle-types.yml @@ -0,0 +1,25 @@ +name: Bundle Types + +on: + push: + tags: + - 'v*' # Run workflow on version tags, e.g. v1.0.0. + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# These permissions are needed to assume roles from Github's OIDC. +permissions: + contents: read + id-token: write + +jobs: + bundle-types: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: grafana/plugin-actions/bundle-types@main + with: + entry-point: ./src/externalComponents/types.ts + ts-config: ./tsconfig-for-bundle-types.json \ No newline at end of file diff --git a/src/exposedComponents/OpenInExploreTracesButton/OpenInExploreTracesButton.tsx b/src/exposedComponents/OpenInExploreTracesButton/OpenInExploreTracesButton.tsx new file mode 100644 index 0000000..c1f1fa7 --- /dev/null +++ b/src/exposedComponents/OpenInExploreTracesButton/OpenInExploreTracesButton.tsx @@ -0,0 +1,56 @@ +import { useReturnToPrevious } from '@grafana/runtime'; +import { LinkButton } from '@grafana/ui'; +import React, { useMemo } from 'react'; +import { OpenInExploreTracesButtonProps } from '../types'; +import pluginJson from '../../plugin.json'; + +export default function OpenInExploreTracesButton({ + datasourceUid, + matchers, + from, + to, + returnToPreviousSource, + renderButton, +}: OpenInExploreTracesButtonProps) { + const setReturnToPrevious = useReturnToPrevious(); + + const href = useMemo(() => { + let params = new URLSearchParams(); + + if (datasourceUid) { + params.append('var-ds', datasourceUid); + } + + if (from) { + params.append('from', from); + } + + if (to) { + params.append('to', to); + } + + matchers.forEach((streamSelector) => { + params.append('var-filters', `${streamSelector.name}|${streamSelector.operator}|${streamSelector.value}`); + }); + + return `a/${pluginJson.id}/explore?${params.toString()}`; + }, [datasourceUid, from, to, matchers]); + + if (!href) { + return null; + } + + if (renderButton) { + return renderButton({ href }); + } + + return ( + setReturnToPrevious(returnToPreviousSource || 'previous')} + > + Open in Traces drilldown + + ); +} diff --git a/src/exposedComponents/index.tsx b/src/exposedComponents/index.tsx new file mode 100644 index 0000000..f904861 --- /dev/null +++ b/src/exposedComponents/index.tsx @@ -0,0 +1,27 @@ +import { LinkButton } from '@grafana/ui'; +import { OpenInExploreTracesButtonProps } from 'exposedComponents/types'; +import React, { lazy, Suspense } from 'react'; +const OpenInExploreTracesButton = lazy(() => import('exposedComponents/OpenInExploreTracesButton/OpenInExploreTracesButton')); + +function SuspendedOpenInExploreTracesButton(props: OpenInExploreTracesButtonProps) { + return ( + + Open in Traces drilldown + + } + > + + + ); +} + +export const exposedComponents = [ + { + id: 'grafana-exploretraces-app/open-in-explore-traces-button/v1', + title: 'Open in Traces drilldown button', + description: 'A button that opens a traces view in the Traces drilldown app.', + component: SuspendedOpenInExploreTracesButton, + }, +]; diff --git a/src/exposedComponents/types.ts b/src/exposedComponents/types.ts new file mode 100644 index 0000000..6a3ec32 --- /dev/null +++ b/src/exposedComponents/types.ts @@ -0,0 +1,15 @@ + +export type TempoMatcher = { + name: string; + value: string; + operator: '=' | '!=' | '>' | '<'; +}; + +export interface OpenInExploreTracesButtonProps { + datasourceUid?: string; + matchers: TempoMatcher[]; + from?: string; + to?: string; + returnToPreviousSource?: string; + renderButton?: (props: { href: string }) => React.ReactElement; +} diff --git a/src/module.tsx b/src/module.tsx index 102441c..da147ab 100644 --- a/src/module.tsx +++ b/src/module.tsx @@ -4,6 +4,7 @@ import { AppPlugin } from '@grafana/data'; // @ts-ignore new API that is not yet in stable release import { sidecarServiceSingleton_EXPERIMENTAL } from '@grafana/runtime'; import pluginJson from './plugin.json'; +import { exposedComponents } from 'exposedComponents'; const App = lazy(() => import('./components/App/App')); const AppConfig = lazy(() => import('./components/AppConfig/AppConfig')); @@ -23,3 +24,8 @@ export const plugin = new AppPlugin<{}>().setRootPage(App).addConfigPage({ sidecarServiceSingleton_EXPERIMENTAL?.openApp(pluginJson.id, helpers.context); }, }); + + +for (const exposedComponentConfig of exposedComponents) { + plugin.exposeComponent(exposedComponentConfig); +} diff --git a/src/plugin.json b/src/plugin.json index ffb00ba..365f1d7 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -57,6 +57,13 @@ "plugins": [] }, "extensions": { + "exposedComponents": [ + { + "id": "grafana-exploretraces-app/open-in-explore-traces-button/v1", + "title": "Open in Traces drilldown button", + "description": "A button that opens a traces view in the Traces drilldown app." + } + ], "addedLinks": [ { "targets": ["grafana-lokiexplore-app/toolbar-open-related/v1"], diff --git a/tsconfig-for-bundle-types.json b/tsconfig-for-bundle-types.json new file mode 100644 index 0000000..5a37ca0 --- /dev/null +++ b/tsconfig-for-bundle-types.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "declaration": true, + "esModuleInterop": true, + "skipLibCheck": true, + "jsx": "react", + "resolveJsonModule": true + } +} \ No newline at end of file