diff --git a/README.md b/README.md
index e366af57b..9527d4449 100644
--- a/README.md
+++ b/README.md
@@ -75,5 +75,27 @@ Once the docker container started, navigate to http://localhost:3000/a/grafana-l
In order to run the setup locally and build the plugin by your own, follow these steps:
1. `yarn install`
-2. `yarn dev` this builds the plugin continously
+2. `yarn dev` this builds the plugin continuously
3. `yarn server` this spins up the docker setup, including a Loki instance and the fake data generator
+
+## Supported Features
+
+This section outlines the supported features available by page: Service Selection and Service Detail.
+
+### Service Selection
+
+Service Selection is the entry step where users can choose a service. List of features and functionalities:
+
+**1. Fetching of services** - Services are fetched using the Loki [/loki/api/v1/index/volume](https://grafana.com/docs/loki/latest/reference/loki-http-api/#query-log-volume) endpoint and ordered by their volume. Services are re-fetched when the time range significantly changes to ensure correct data. Services are updated if:
+- The time range scope changes (hours vs. days).
+- The new time range is under 6 hours and the difference exceeds 30 minutes.
+- The new time range is under 1 day and the difference exceeds 1 hour.
+- The new time range is over 1 day and the difference exceeds 1 day.
+
+**2. Showing of services** - Services are shown based on volume and are lazy-loaded. Metrics and logs are queried only for services that are scrolled to.
+
+**3. Previously selected services** - Previously selected services are displayed at the top of the list for easier access.
+
+**4. Searching of services** - The search input can be used to filter services that include the specified string.
+
+### Service Details
diff --git a/src/Components/ServiceSelectionScene/ConfigureVolumeError.tsx b/src/Components/ServiceSelectionScene/ConfigureVolumeError.tsx
new file mode 100644
index 000000000..1a3e1dc5a
--- /dev/null
+++ b/src/Components/ServiceSelectionScene/ConfigureVolumeError.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { GrotError } from 'Components/GrotError';
+import { TextLink, Text } from '@grafana/ui';
+
+export const ConfigureVolumeError = () => {
+ return (
+
+
Log volume has not been configured.
+
+
+ Instructions to enable volume in the Loki config:
+
+
+
+
+
+ limits_config:
+
+ volume_enabled: true
+
+
+
+
+ );
+};
diff --git a/src/Components/ServiceSelectionScene/ServiceSelectionScene.tsx b/src/Components/ServiceSelectionScene/ServiceSelectionScene.tsx
index ffa9f0de2..966d43672 100644
--- a/src/Components/ServiceSelectionScene/ServiceSelectionScene.tsx
+++ b/src/Components/ServiceSelectionScene/ServiceSelectionScene.tsx
@@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import { debounce } from 'lodash';
import React, { useCallback, useState } from 'react';
-import { BusEventBase, GrafanaTheme2 } from '@grafana/data';
+import { BusEventBase, GrafanaTheme2, TimeRange } from '@grafana/data';
import {
AdHocFiltersVariable,
PanelBuilders,
@@ -14,27 +14,17 @@ import {
SceneVariable,
VariableDependencyConfig,
} from '@grafana/scenes';
-import {
- DrawStyle,
- Field,
- Icon,
- Input,
- LoadingPlaceholder,
- StackingMode,
- Text,
- TextLink,
- useStyles2,
-} from '@grafana/ui';
+import { DrawStyle, Field, Icon, Input, LoadingPlaceholder, StackingMode, useStyles2 } from '@grafana/ui';
import { getLokiDatasource } from 'services/scenes';
import { getFavoriteServicesFromStorage } from 'services/store';
import { testIds } from 'services/testIds';
import { LEVEL_VARIABLE_VALUE, VAR_DATASOURCE, VAR_FILTERS } from 'services/variables';
-import { GrotError } from '../GrotError';
import { SelectFieldButton } from './SelectFieldButton';
import { PLUGIN_ID } from 'services/routing';
import { buildLokiQuery } from 'services/query';
import { USER_EVENTS_ACTIONS, USER_EVENTS_PAGES, reportAppInteraction } from 'services/analytics';
import { getQueryRunner, setLeverColorOverrides } from 'services/panel';
+import { ConfigureVolumeError } from './ConfigureVolumeError';
export const SERVICE_NAME = 'service_name';
@@ -62,7 +52,8 @@ export class ServiceSelectionComponent extends SceneObjectBase {
const { name } = variable.state;
if (name === VAR_DATASOURCE) {
- this._getServicesByVolume();
+ // If datasource changes, we need to fetch services by volume for the new datasource
+ this.getServicesByVolume();
}
},
});
@@ -88,15 +79,16 @@ export class ServiceSelectionComponent extends SceneObjectBase {
- // Updates servicesToQuery when servicesByVolume is changed - should happen only once when the list of services is fetched during initialization
+ // Updates servicesToQuery when servicesByVolume is changed
if (newState.servicesByVolume !== oldState.servicesByVolume) {
- const ds = sceneGraph.lookupVariable(VAR_DATASOURCE, this)?.getValue();
- const servicesToQuery = createListOfServicesToQuery(
- newState.servicesByVolume ?? [],
- getFavoriteServicesFromStorage(ds)
- );
+ const ds = sceneGraph.lookupVariable(VAR_DATASOURCE, this)?.getValue()?.toString();
+ let servicesToQuery: string[] = [];
+ if (ds && newState.servicesByVolume) {
+ servicesToQuery = createListOfServicesToQuery(newState.servicesByVolume, ds, this.state.searchServicesString);
+ }
this.setState({
servicesToQuery,
});
@@ -104,14 +96,10 @@ export class ServiceSelectionComponent extends SceneObjectBase
- service.toLowerCase().includes(newState.searchServicesString?.toLowerCase() ?? '')
- );
- let servicesToQuery = services ?? [];
- // If user is not searching for anything, add favorite services to the top
- if (newState.searchServicesString === '') {
- const ds = sceneGraph.lookupVariable(VAR_DATASOURCE, this)?.getValue();
- servicesToQuery = createListOfServicesToQuery(servicesToQuery, getFavoriteServicesFromStorage(ds));
+ const ds = sceneGraph.lookupVariable(VAR_DATASOURCE, this)?.getValue()?.toString();
+ let servicesToQuery: string[] = [];
+ if (ds && this.state.servicesByVolume) {
+ servicesToQuery = createListOfServicesToQuery(this.state.servicesByVolume, ds, newState.searchServicesString);
}
this.setState({
servicesToQuery,
@@ -123,10 +111,16 @@ export class ServiceSelectionComponent extends SceneObjectBase {
+ if (shouldUpdateServicesByVolume(newTime.value, oldTime.value)) {
+ this.getServicesByVolume();
+ }
+ });
}
- // Run on initialization to fetch list of services ordered by volume
- private async _getServicesByVolume() {
+ // Run to fetch services by volume
+ private async getServicesByVolume() {
const timeRange = sceneGraph.getTimeRange(this).state.value;
this.setState({
isServicesByVolumeLoading: true,
@@ -181,9 +175,10 @@ export class ServiceSelectionComponent extends SceneObjectBase= 4 && timeRange.to.diff(timeRange.from, 'hours') <= 26) {
+ splitDuration = '2h';
+ }
return new SceneCSSGridItem({
body: PanelBuilders.timeseries()
// If service was previously selected, we show it in the title
@@ -213,7 +212,7 @@ export class ServiceSelectionComponent extends SceneObjectBase