Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cloudwatch: Add back support for old Log Group picker #73524

Merged
merged 4 commits into from Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions pkg/tsdb/cloudwatch/metric_find_query.go
Expand Up @@ -8,10 +8,12 @@ import (
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/grafana/grafana-plugin-sdk-go/backend"
Expand Down Expand Up @@ -282,3 +284,38 @@ func (e *cloudWatchExecutor) resourceGroupsGetResources(ctx context.Context, plu

return &resp, nil
}

// legacy route, will be removed once GovCloud supports Cross Account Observability
func (e *cloudWatchExecutor) handleGetLogGroups(ctx context.Context, pluginCtx backend.PluginContext, parameters url.Values) ([]suggestData, error) {
region := parameters.Get("region")
limit := parameters.Get("limit")
logGroupNamePrefix := parameters.Get("logGroupNamePrefix")

logsClient, err := e.getCWLogsClient(ctx, pluginCtx, region)
if err != nil {
return nil, err
}

logGroupLimit := defaultLogGroupLimit
intLimit, err := strconv.ParseInt(limit, 10, 64)
if err == nil && intLimit > 0 {
logGroupLimit = intLimit
}

input := &cloudwatchlogs.DescribeLogGroupsInput{Limit: aws.Int64(logGroupLimit)}
if len(logGroupNamePrefix) > 0 {
input.LogGroupNamePrefix = aws.String(logGroupNamePrefix)
}
var response *cloudwatchlogs.DescribeLogGroupsOutput
response, err = logsClient.DescribeLogGroups(input)
if err != nil || response == nil {
return nil, err
}
result := make([]suggestData, 0)
for _, logGroup := range response.LogGroups {
logGroupName := *logGroup.LogGroupName
result = append(result, suggestData{Text: logGroupName, Value: logGroupName, Label: logGroupName})
}

return result, nil
}
3 changes: 3 additions & 0 deletions pkg/tsdb/cloudwatch/resource_handler.go
Expand Up @@ -28,6 +28,9 @@ func (e *cloudWatchExecutor) newResourceMux() *http.ServeMux {
mux.HandleFunc("/log-group-fields", routes.ResourceRequestMiddleware(routes.LogGroupFieldsHandler, logger, e.getRequestContext))
mux.HandleFunc("/external-id", routes.ResourceRequestMiddleware(routes.ExternalIdHandler, logger, e.getRequestContext))

// remove this once AWS's Cross Account Observability is supported in GovCloud
mux.HandleFunc("/legacy-log-groups", handleResourceReq(e.handleGetLogGroups))

return mux
}

Expand Down
Expand Up @@ -39,6 +39,9 @@ jest.mock('@grafana/runtime', () => ({
config: {
...jest.requireActual('@grafana/runtime').config,
awsAssumeRoleEnabled: true,
featureToggles: {
cloudWatchCrossAccountQuerying: true,
},
},
}));

Expand Down Expand Up @@ -166,7 +169,7 @@ describe('Render', () => {

it('should display log group selector field', async () => {
setup();
await waitFor(async () => expect(await screen.getByText('Select log groups')).toBeInTheDocument());
await waitFor(async () => expect(screen.getByText('Select log groups')).toBeInTheDocument());
});

it('should only display the first two default log groups and show all of them when clicking "Show all" button', async () => {
Expand Down Expand Up @@ -204,9 +207,7 @@ describe('Render', () => {
});

it('should show error message if Select log group button is clicked when data source is never saved', async () => {
const SAVED_VERSION = undefined;
setup({ version: SAVED_VERSION });

setup({ version: 1 });
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
await userEvent.click(screen.getByText('Select log groups'));
await waitFor(() =>
Expand Down Expand Up @@ -265,12 +266,11 @@ describe('Render', () => {
});

it('should open log group selector if Select log group button is clicked when data source has saved changes', async () => {
const SAVED_VERSION = undefined;
const newProps = {
...props,
options: {
...props.options,
version: SAVED_VERSION,
version: 1,
},
};
const meta: PluginMeta = {
Expand All @@ -291,7 +291,7 @@ describe('Render', () => {
...newProps,
options: {
...newProps.options,
version: 1,
version: 2,
},
};
rerender(
Expand Down
Expand Up @@ -21,7 +21,7 @@ import { store } from 'app/store/store';
import { CloudWatchDatasource } from '../../datasource';
import { SelectableResourceValue } from '../../resources/types';
import { CloudWatchJsonData, CloudWatchSecureJsonData } from '../../types';
import { LogGroupsField } from '../shared/LogGroups/LogGroupsField';
import { LogGroupsFieldWrapper } from '../shared/LogGroups/LogGroupsField';

import { XrayLinkConfig } from './XrayLinkConfig';

Expand Down Expand Up @@ -124,39 +124,47 @@ export const ConfigEditor = (props: Props) => {
shrink={true}
{...logGroupFieldState}
>
<LogGroupsField
region={defaultRegion ?? ''}
datasource={datasource}
onBeforeOpen={() => {
if (saved) {
return;
}

let error = 'You need to save the data source before adding log groups.';
if (props.options.version && props.options.version > 1) {
error =
'You have unsaved connection detail changes. You need to save the data source before adding log groups.';
}
setLogGroupFieldState({
invalid: true,
error,
});
throw new Error(error);
}}
legacyLogGroupNames={defaultLogGroups}
logGroups={logGroups}
onChange={(updatedLogGroups) => {
onOptionsChange({
...props.options,
jsonData: {
...props.options.jsonData,
logGroups: updatedLogGroups,
defaultLogGroups: undefined,
},
});
}}
maxNoOfVisibleLogGroups={2}
/>
{datasource ? (
<LogGroupsFieldWrapper
region={defaultRegion ?? ''}
datasource={datasource}
onBeforeOpen={() => {
if (saved) {
return;
}

let error = 'You need to save the data source before adding log groups.';
if (props.options.version && props.options.version > 1) {
error =
'You have unsaved connection detail changes. You need to save the data source before adding log groups.';
}
setLogGroupFieldState({
invalid: true,
error,
});
throw new Error(error);
}}
legacyLogGroupNames={defaultLogGroups}
logGroups={logGroups}
onChange={(updatedLogGroups) => {
onOptionsChange({
...props.options,
jsonData: {
...props.options.jsonData,
logGroups: updatedLogGroups,
defaultLogGroups: undefined,
},
});
}}
maxNoOfVisibleLogGroups={2}
//legacy props
legacyOnChange={(logGroups) => {
updateDatasourcePluginJsonDataOption(props, 'defaultLogGroups', logGroups);
}}
/>
) : (
<></>
)}
</InlineField>
</div>
<XrayLinkConfig
Expand Down Expand Up @@ -242,7 +250,7 @@ function useDataSourceSavedState(props: Props) {
]);

useEffect(() => {
props.options.version && setSaved(true);
props.options.version && props.options.version > 1 && setSaved(true);
}, [props.options.version]);

return saved;
Expand Down
Expand Up @@ -10,7 +10,7 @@ import { TRIGGER_SUGGEST } from '../../../language/monarch/commands';
import { registerLanguage } from '../../../language/monarch/register';
import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../../../types';
import { getStatsGroups } from '../../../utils/query/getStatsGroups';
import { LogGroupsField } from '../../shared/LogGroups/LogGroupsField';
import { LogGroupsFieldWrapper } from '../../shared/LogGroups/LogGroupsField';

export interface CloudWatchLogsQueryFieldProps
extends QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData>,
Expand Down Expand Up @@ -47,14 +47,18 @@ export const CloudWatchLogsQueryFieldMonaco = (props: CloudWatchLogsQueryFieldPr

return (
<>
<LogGroupsField
<LogGroupsFieldWrapper
region={query.region}
datasource={datasource}
legacyLogGroupNames={query.logGroupNames}
logGroups={query.logGroups}
onChange={(logGroups) => {
onChange({ ...query, logGroups, logGroupNames: undefined });
}}
//legacy props
legacyOnChange={(logGroupNames) => {
onChange({ ...query, logGroupNames });
}}
/>
<div className="gf-form-inline gf-form-inline--nowrap flex-grow-1">
<div className="gf-form--grow flex-shrink-1">
Expand Down
Expand Up @@ -18,9 +18,9 @@ import {
// dom also includes Element polyfills
import { CloudWatchDatasource } from '../../../datasource';
import syntax from '../../../language/cloudwatch-logs/syntax';
import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery } from '../../../types';
import { CloudWatchJsonData, CloudWatchLogsQuery, CloudWatchQuery, LogGroup } from '../../../types';
import { getStatsGroups } from '../../../utils/query/getStatsGroups';
import { LogGroupsField } from '../../shared/LogGroups/LogGroupsField';
import { LogGroupsFieldWrapper } from '../../shared/LogGroups/LogGroupsField';

export interface CloudWatchLogsQueryFieldProps
extends QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData>,
Expand Down Expand Up @@ -81,14 +81,18 @@ export const CloudWatchLogsQueryField = (props: CloudWatchLogsQueryFieldProps) =

return (
<>
<LogGroupsField
<LogGroupsFieldWrapper
region={query.region}
datasource={datasource}
legacyLogGroupNames={query.logGroupNames}
logGroups={query.logGroups}
onChange={(logGroups) => {
onChange={(logGroups: LogGroup[]) => {
onChange({ ...query, logGroups, logGroupNames: undefined });
}}
//legacy props can be removed once we remove support for Legacy Log Group Selector
legacyOnChange={(logGroups: string[]) => {
onChange({ ...query, logGroupNames: logGroups });
}}
/>
<div className="gf-form-inline gf-form-inline--nowrap flex-grow-1">
<div className="gf-form gf-form--grow flex-shrink-1">
Expand Down
Expand Up @@ -45,6 +45,16 @@ jest.mock('./MetricsQueryEditor/SQLCodeEditor', () => ({
},
}));

jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
config: {
...jest.requireActual('@grafana/runtime').config,
featureToggles: {
cloudWatchCrossAccountQuerying: true,
},
},
}));

export { SQLCodeEditor } from './MetricsQueryEditor/SQLCodeEditor';

describe('QueryEditor should render right editor', () => {
Expand Down
@@ -0,0 +1,30 @@
import { css } from '@emotion/css';
import React from 'react';

import { CloudWatchDatasource } from '../../../datasource';

import { LogGroupSelector } from './LegacyLogGroupSelector';

type Props = {
datasource: CloudWatchDatasource;
onChange: (logGroups: string[]) => void;
region: string;
legacyLogGroupNames: string[];
};

const rowGap = css`
gap: 3px;
`;

export const LegacyLogGroupSelection = ({ datasource, region, legacyLogGroupNames, onChange }: Props) => {
return (
<div className={`gf-form gf-form--grow flex-grow-1 ${rowGap}`}>
<LogGroupSelector
region={region}
selectedLogGroups={legacyLogGroupNames}
datasource={datasource}
onChange={onChange}
/>
</div>
);
};