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

Feat/rework config page #990

Merged
merged 33 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
65c53c8
chore: migrate to `react-router-dom@v6` (with `react-router-dom-v5-co…
w1kman Nov 1, 2024
af7d9a3
chore: update summary table links
w1kman Nov 1, 2024
652ca8d
chore: create-plugin update
w1kman Nov 1, 2024
afa8f54
chore: fix more tests
w1kman Nov 4, 2024
0441a35
chore: redirect to `home`
w1kman Nov 4, 2024
b733f02
chore: update to node `v20`
w1kman Nov 5, 2024
290ffdd
feat: secrets management
w1kman Nov 5, 2024
a008ce4
fix: add missing line break
w1kman Nov 5, 2024
8fd524a
fix: self-review change requests
w1kman Nov 5, 2024
63a338b
fix: self-review change requests
w1kman Nov 5, 2024
2283067
Merge branch 'chore/update-react-router-dom' into feat/secrets-design…
w1kman Nov 6, 2024
d3e4ce7
feat: add dedicated `pluginConfig` page
w1kman Nov 7, 2024
3cf6f28
chore: clean up
w1kman Nov 16, 2024
f6668e7
chore: migrate to `react-router-dom@v6` (with `react-router-dom-v5-co…
w1kman Nov 1, 2024
2759301
chore: update summary table links
w1kman Nov 1, 2024
efb0dd6
chore: create-plugin update
w1kman Nov 1, 2024
beddd64
chore: fix more tests
w1kman Nov 4, 2024
4518ece
chore: redirect to `home`
w1kman Nov 4, 2024
cb0fde6
chore: update to node `v20`
w1kman Nov 5, 2024
1b6e937
fix: add missing line break
w1kman Nov 5, 2024
a60ed0d
fix: self-review change requests
w1kman Nov 5, 2024
ff2e734
fix: self-review change requests
w1kman Nov 5, 2024
f084795
fix: pr change requests
w1kman Nov 14, 2024
3189eb3
chore: update `yarn.lock` after rebase
w1kman Nov 19, 2024
e3d71a1
Merge branch 'chore/update-react-router-dom' into feat/rework-config-…
w1kman Nov 19, 2024
d70572e
fix: remove comment
w1kman Nov 19, 2024
faed25d
Merge branch 'main' into feat/rework-config-page
w1kman Nov 19, 2024
f714241
fix: self-review
w1kman Nov 19, 2024
ae52408
fix: chris CRs
w1kman Nov 21, 2024
cecd58e
fix: chris CRs
w1kman Nov 22, 2024
14bea49
fix: re-style headings on config page
w1kman Nov 22, 2024
e708beb
fix: replace CSS syntax for object syntax
w1kman Nov 22, 2024
c2588f5
fix: vikas CRs
w1kman Nov 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 0 additions & 72 deletions src/components/AccessToken.tsx

This file was deleted.

25 changes: 0 additions & 25 deletions src/components/BackendAddress/BackendAddress.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/components/BackendAddress/index.ts

This file was deleted.

19 changes: 8 additions & 11 deletions src/components/Clipboard/Clipboard.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import React from 'react';
import { AppEvents } from '@grafana/data';
import { useStyles } from '@grafana/ui';
import { useStyles2 } from '@grafana/ui';
import appEvents from 'grafana/app/core/app_events';
import { css, cx } from '@emotion/css';

import { Preformatted } from '../Preformatted';
import { CopyToClipboard } from './CopyToClipboard';

const getStyles = () => ({
code: css`
width: 100%;
word-break: break-all;
overflow-y: scroll;
max-height: 100%;
`,
container: css`
display: flex;
flex-direction: column;
Expand All @@ -26,16 +21,18 @@ interface Props {
content: string;
className?: string;
truncate?: boolean;
highlight?: string | string[];
isCode?: boolean;
}

export function Clipboard({ content, className, truncate }: Props) {
const styles = useStyles(getStyles);
export function Clipboard({ content, className, truncate, highlight, isCode }: Props) {
const styles = useStyles2(getStyles);

return (
<div className={cx(styles.container, className)}>
<pre className={styles.code} data-testid="clipboard-content">
<Preformatted isCode={isCode} highlight={highlight} data-testid="clipboard-content">
{truncate ? content.slice(0, 150) + '...' : content}
</pre>
</Preformatted>

<CopyToClipboard
className={styles.button}
Expand Down
21 changes: 0 additions & 21 deletions src/components/ConfigPageWrapper.tsx

This file was deleted.

38 changes: 28 additions & 10 deletions src/components/LinkedDatasourceView.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,63 @@
import React from 'react';
import { Alert, Card, Tag } from '@grafana/ui';

import { useCanWriteLogs, useCanWriteMetrics } from 'hooks/useDSPermission';
import { useCanWriteLogs, useCanWriteMetrics, useCanWriteSM } from 'hooks/useDSPermission';
import { useLogsDS } from 'hooks/useLogsDS';
import { useMetricsDS } from 'hooks/useMetricsDS';
import { useSMDS } from 'hooks/useSMDS';

interface LinkedDatasourceViewProps {
type: 'loki' | 'prometheus';
type: 'loki' | 'prometheus' | 'synthetic-monitoring-datasource';
}

export const LinkedDatasourceView = ({ type }: LinkedDatasourceViewProps) => {
const metricsDS = useMetricsDS();
const logsDS = useLogsDS();
const smDS = useSMDS();

const canEditSM = useCanWriteSM();
const canEditLogs = useCanWriteLogs();
const canEditMetrics = useCanWriteMetrics();

const canEditMap = {
prometheus: canEditMetrics,
loki: canEditLogs,
'synthetic-monitoring-datasource': canEditSM,
};

const dsMap = {
prometheus: metricsDS,
loki: logsDS,
'synthetic-monitoring-datasource': smDS,
};

const ds = dsMap[type];

if (!ds) {
return (
<Alert title="Data source missing">
&quot;{type}&quot; data source is missing. Please configure it in the data sources settings.
ckbedwell marked this conversation as resolved.
Show resolved Hide resolved
</Alert>
);
return null;
}

const showHref = canEditMap[type];
const Tag = showHref ? 'a' : 'div';

return (
<Tag className="add-data-source-item" href={showHref ? `datasources/edit/${ds.uid}/` : undefined}>
<img className="add-data-source-item-logo" src={ds.meta.info.logos.small} alt="" />
<div className="add-data-source-item-text-wrapper">
<span className="add-data-source-item-text">{ds.name}</span>
<span className="add-data-source-item-desc">{ds.type}</span>
</div>
</Tag>
<Card href={showHref ? `datasources/edit/${ds.uid}/` : undefined}>
<Card.Heading>{ds.name}</Card.Heading>
<Card.Figure>
<img width={40} height={40} src={ds.meta.info.logos.small} alt="" />
</Card.Figure>

{type !== 'synthetic-monitoring-datasource' && (
<Card.Tags>
<Tag name="Linked" />
</Card.Tags>
)}

<Card.Meta>{ds.type}</Card.Meta>
</Card>
);
};
77 changes: 77 additions & 0 deletions src/components/Preformatted.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// @ts-ignore
import React from 'react';
import { render, screen, within } from '@testing-library/react';

// @ts-ignore
w1kman marked this conversation as resolved.
Show resolved Hide resolved
import { Preformatted } from './Preformatted';

describe('Preformatted', () => {
it('should render content within a pre tag', async () => {
const sampleContent = '__sample_content__';
render(<Preformatted>{sampleContent}</Preformatted>);
const pre = screen.queryByText(sampleContent);

expect(pre).toBeInTheDocument();
});

it('should render content within a pre tag, within a code tag', async () => {
const sampleContent = '__sample_content__';
render(<Preformatted isCode>{sampleContent}</Preformatted>);
const pre = screen.queryByText(sampleContent, { selector: 'code' });

expect(pre).toBeInTheDocument();
});

it.each([
{
highlight: '__sample__',
content: `This is a __sample__ content.`, // Note: 'This' is used to find the pre tag
expectedLength: 1,
},
{
highlight: '__target__',
content: `This should highlight __target__ and __target__`, // Note: 'This' is used to find the pre tag
expectedLength: 2,
},
])(
'should take string as highlight prop ($expectedLength counts)',
async ({ highlight, content, expectedLength }) => {
render(<Preformatted highlight={highlight}>{content}</Preformatted>);

const pre = screen.getByText(/^This/, { selector: 'pre' });
expect(pre).toBeInTheDocument();
const highlighted = pre && within(pre).queryAllByText(highlight, { selector: 'strong' });
expect(highlighted).toHaveLength(expectedLength);
}
);

it.each([
{
highlight: ['__sample__'],
content: `This should replace __sample__`, // Note: 'This' is used to find the pre tag
expectedLength: 1,
},
{
highlight: ['__sample__', '__target__'],
content: `This should replace __sample__ and __target__`, // Note: 'This' is used to find the pre tag
expectedLength: 2,
},
{
highlight: ['__sample__', '__target__', '__another__'],
content: `This should replace __sample__, __another__ and __target__`, // Note: 'This' is used to find the pre tag
expectedLength: 3,
},
])('should take array as highlight prop ($expectedLength counts)', async ({ highlight, content, expectedLength }) => {
render(<Preformatted highlight={highlight}>{content}</Preformatted>);

const pre = screen.getByText(/^This/, { selector: 'pre' });
expect(pre).toBeInTheDocument();

const count = highlight.reduce((acc, word) => {
const match = pre && within(pre).queryAllByText(word, { selector: 'strong' });
return acc + match.length;
}, 0);

expect(count).toBe(expectedLength);
});
});
66 changes: 66 additions & 0 deletions src/components/Preformatted.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { Children, Fragment, PropsWithChildren, ReactNode } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { css, cx } from '@emotion/css';
import { DataTestIds } from 'test/dataTestIds';

function highlightCode(children: ReactNode, highlight?: string): ReactNode {
if (!highlight) {
return children;
}
const elements = Children.toArray(children);
return elements.map((child) => {
if (typeof child === 'string') {
return child
.split(highlight)
.flatMap((item, index) => [item, <strong key={`${highlight}.${index}`}>{highlight}</strong>])
.slice(0, -1);
}
return child;
});
}

function doHighlights(children: ReactNode, highlights?: string | string[]): ReactNode {
const highlightsArray = Array.isArray(highlights) ? highlights : [highlights];

return highlights
? highlightsArray.reduce((acc, currentValue, currentIndex) => {
return highlightCode(acc, currentValue);
}, children)
: children;
}

export function Preformatted({
children,
className,
highlight,
isCode = false,
}: PropsWithChildren<{ className?: string; highlight?: string | string[]; isCode?: boolean }>) {
const styles = useStyles2(getStyles);
const Wrapper = isCode ? 'code' : Fragment;

return (
<pre data-testid={DataTestIds.PREFORMATTED} className={cx(styles.container, className)}>
<Wrapper>{doHighlights(children, highlight)}</Wrapper>
</pre>
);
}

function getStyles(theme: GrafanaTheme2) {
return {
container: css({
overflowY: 'auto',
maxHeight: '100%',
whiteSpace: 'pre-wrap',
backgroundColor: theme.colors.background.canvas,
marginBottom: theme.spacing(2),
'& strong': {
color: theme.colors.warning.text,
},
'& code': {
padding: 0,
margin: 0,
},
}),
};
}
2 changes: 1 addition & 1 deletion src/components/ProbeTokenModal/ProbeTokenModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type TokenModalProps = {

export const ProbeTokenModal = ({ actionText, isOpen, onDismiss, token }: TokenModalProps) => {
return (
<Modal isOpen={isOpen} title="Probe Authentication Token" onDismiss={onDismiss}>
<Modal isOpen={isOpen} title="Probe access token" onDismiss={onDismiss}>
<Alert severity="warning" title="Note">
This is the only time you will see this token. If you need to view it again, you will need to reset the token.
</Alert>
Expand Down
Loading
Loading