Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"check-cycles": "CHECK_CYCLES=true yarn dev-once",
"coverage": "jest --coverage .",
"eslint": "node ./node_modules/.bin/eslint --max-warnings ${MAX_WARNINGS:-0} --color",
"lint": "NODE_OPTIONS=--max-old-space-size=4096 MAX_WARNINGS=341 yarn eslint --format ./scripts/eslint-exact-warnings.js .",
"lint": "NODE_OPTIONS=--max-old-space-size=4096 MAX_WARNINGS=330 yarn eslint --format ./scripts/eslint-exact-warnings.js .",
"gherkin-lint": "./node_modules/.bin/gherkin-lint -c ./packages/dev-console/integration-tests/.gherkin-lintrc ./packages/*/integration-tests/features",
"test": "LANG=en_US.UTF-8 jest",
"debug-test": "node --inspect-brk node_modules/.bin/jest --runInBand",
Expand Down
47 changes: 26 additions & 21 deletions frontend/packages/console-app/src/hooks/usePluginRoutes.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FC, ReactElement, ComponentType } from 'react';
import type { FC, ReactElement, ComponentType, LazyExoticComponent } from 'react';
import { useMemo, lazy, useEffect, Suspense } from 'react';
import type { RouteProps } from 'react-router';
import { createPath, Route, useLocation } from 'react-router';
Expand All @@ -13,29 +13,34 @@ const isRoutePageExtensionActive: IsRouteExtensionActive = (extension, activePer
(extension.properties.perspective ?? activePerspective) === activePerspective;

// Cache lazy components by extension UID to prevent recreation on re-renders
const lazyComponentCache = new Map<string, React.LazyExoticComponent<ComponentType<any>>>();
const lazyComponentCache = new Map<string, LazyExoticComponent<ComponentType<unknown>>>();

const getOrCreateLazyComponent = (
uid: string,
component: () => Promise<ComponentType<any>>,
pluginName: string,
): LazyExoticComponent<ComponentType<unknown>> => {
if (!lazyComponentCache.has(uid)) {
lazyComponentCache.set(
uid,
lazy(async () => {
const Component = await component();
// Check falsy to determine if the component wasn't loaded
if (!Component) {
throw new Error(
`Plugin "${pluginName}" route component resolved to ${typeof Component} (extension ${uid})`,
);
}
return { default: Component };
}),
);
}
return lazyComponentCache.get(uid);
};

const LazyRoutePage: FC<LazyRoutePageProps> = ({ extension }) => {
const { pluginName, uid, properties } = extension;
const { component } = properties;
const LazyComponent = useMemo(() => {
if (!lazyComponentCache.has(uid)) {
lazyComponentCache.set(
uid,
lazy(async () => {
const Component = await component();
// Check falsy to determine if the component wasn't loaded
if (!Component) {
throw new Error(
`Plugin "${pluginName}" route component resolved to ${typeof Component} (extension ${uid})`,
);
}
return { default: Component };
}),
);
}
return lazyComponentCache.get(uid);
}, [uid, component, pluginName]);
const LazyComponent = getOrCreateLazyComponent(uid, properties.component, pluginName);

return (
<Suspense fallback={<LoadingBox blame={`${pluginName}: ${extension.uid}`} />}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,14 @@ const IFrameMarkdownView: FC<InnerSyncMarkdownProps> = ({
[THEME_GLASS_CLASS]: contrast === THEME_GLASS,
});

// eslint-disable-next-line react-hooks/exhaustive-deps
const updateDimensions = useCallback(
_.debounce(() => {
const el = frameRef.current?.contentWindow?.document?.body?.firstElementChild;
if (el) {
setFrameHeight(el.scrollHeight + (exactHeight ? 0 : 15));
}
}, 100),
const updateDimensions = useMemo(
() =>
_.debounce(() => {
const el = frameRef.current?.contentWindow?.document?.body?.firstElementChild;
if (el) {
setFrameHeight(el.scrollHeight + (exactHeight ? 0 : 15));
}
}, 100),
[exactHeight],
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactElement, FC } from 'react';
import { useRef, useState, useMemo, memo } from 'react';
import { useRef, useMemo, memo } from 'react';
import { ChartDonut } from '@patternfly/react-charts/victory';
import { Tooltip } from '@patternfly/react-core';
import * as _ from 'lodash';
Expand Down Expand Up @@ -56,11 +56,10 @@ const PodStatusBase: FC<PodStatusProps> = ({
data,
}) => {
const ref = useRef();
const [updateOnEnd, setUpdateOnEnd] = useState<boolean>(false);
const forceUpdate = useForceUpdate();
const prevVData = useRef<PodData[]>(null);

const vData = useMemo(() => {
const { vData, updateOnEnd } = useMemo(() => {
const updateVData: PodData[] = podStatus.map((pod) => ({
x: pod,
y: _.sumBy(data, (d) => +(getPodStatus(d) === pod)) || 0,
Expand All @@ -76,13 +75,13 @@ const PodStatusBase: FC<PodStatusProps> = ({

const prevDataPoints = _.size(_.filter(prevVData.current, (nextData) => nextData.y !== 0));
const dataPoints = _.size(_.filter(updateVData, (nextData) => nextData.y !== 0));
setUpdateOnEnd(dataPoints === 1 && prevDataPoints > 1);
const shouldUpdateOnEnd = dataPoints === 1 && prevDataPoints > 1;

if (!_.isEqual(prevVData.current, updateVData)) {
prevVData.current = updateVData;
return updateVData;
return { vData: updateVData, updateOnEnd: shouldUpdateOnEnd };
}
return prevVData.current;
return { vData: prevVData.current, updateOnEnd: shouldUpdateOnEnd };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
const truncTitle = title ? _.truncate(title, { length: MAX_POD_TITLE_LENGTH }) : undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { FC, Ref, ReactNode, KeyboardEvent, MouseEvent } from 'react';
import { memo, useState, useEffect, useCallback, useLayoutEffect } from 'react';
import { memo, useState, useEffect, useCallback, useMemo, useLayoutEffect } from 'react';
import {
Chart,
ChartArea,
Expand Down Expand Up @@ -115,8 +115,7 @@ const SpanControls = memo<SpanControlsProps>(
setText(formatPrometheusDuration(span));
}, [span]);

// eslint-disable-next-line react-hooks/exhaustive-deps
const debouncedOnChange = useCallback(_.debounce(onChange, 400), [onChange]);
const debouncedOnChange = useMemo(() => _.debounce(onChange, 400), [onChange]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mechanically the same thing as useCallback and does nothing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — the useMemo wrapper is functionally equivalent to useCallback here. However, reverting to useCallback triggers a lint error (React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead) since _.debounce() returns a non-inline function. Keeping useMemo with the inline factory avoids that lint error while being semantically equivalent. Removed the unnecessary eslint-disable comment either way.


const setSpan = (newText: string, isDebounced = false) => {
const newSpan = parsePrometheusDuration(newText);
Expand Down Expand Up @@ -366,7 +365,7 @@ const Graph = memo<GraphProps>(
const legendData: { name: string }[] = [];
const { t } = useTranslation('console-shared');

const [xDomain, setXDomain] = useState(fixedXDomain || getXDomain(Date.now(), span));
const [xDomain, setXDomain] = useState(() => fixedXDomain || getXDomain(Date.now(), span));

// Only update X-axis if the time range (fixedXDomain or span) or graph data (allSeries) change
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,21 @@ export const useDevfileServer = (

const { devfileContent, devfilePath } = devfile || {};

const devfileDataPromise = useMemo(async () => {
const devfileDataPromise = useMemo(() => {
if (!name || !url || !devfileContent) {
return null;
return Promise.resolve(null);
}

const newDevfileContent = await convertURItoInlineYAML(
devfileContent,
url,
ref,
dir,
type,
secretResource,
return convertURItoInlineYAML(devfileContent, url, ref, dir, type, secretResource).then(
(newDevfileContent) => ({
name,
git: { URL: url, ref, dir: prefixDotSlash(dir) },
devfile: {
devfileContent: newDevfileContent,
devfilePath: `${smartSlashDir}${devfilePath}`,
},
}),
);

return {
name,
git: { URL: url, ref, dir: prefixDotSlash(dir) },
devfile: { devfileContent: newDevfileContent, devfilePath: `${smartSlashDir}${devfilePath}` },
};
}, [name, url, devfileContent, ref, dir, type, secretResource, smartSlashDir, devfilePath]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,60 @@ const AccessTokenDocLinks = {
[GitProvider.BITBUCKET]: 'https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/',
};

interface WebhookHelpTextProps {
gitProvider: GitProvider;
testId: string;
}

const WebhookHelpText: FC<WebhookHelpTextProps> = ({ gitProvider, testId }): ReactElement => {
const { t } = useTranslation('devconsole');
let helpText: ReactNode;
switch (gitProvider) {
case GitProvider.GITHUB:
helpText = (
<Trans t={t} ns="devconsole">
Use your GitHub Personal token. Use this{' '}
<ExternalLink href={AccessTokenDocLinks[GitProvider.GITHUB]}>link</ExternalLink> to create
a <strong>classic</strong> token with <strong>repo</strong> & <strong>admin:repo_hook</strong> scopes and give your
token an expiration, i.e 30d.
</Trans>
);
break;

case GitProvider.GITLAB:
helpText = (
<Trans t={t} ns="devconsole">
Use your Gitlab Personal access token. Use this{' '}
<ExternalLink href={AccessTokenDocLinks[GitProvider.GITLAB]}>link</ExternalLink> to create
a token with <strong>api</strong> scope. Select the role as <strong>Maintainer/Owner</strong>. Give your token
an expiration i.e 30d.
</Trans>
);
break;

case GitProvider.BITBUCKET:
helpText = (
<Trans t={t} ns="devconsole">
Use your Bitbucket App password. Use this{' '}
<ExternalLink href={AccessTokenDocLinks[GitProvider.BITBUCKET]}>link</ExternalLink> to
create a token with <strong>Read and Write </strong>scopes in{' '}
<strong>Account, Workspace membership, Projects, Issues, Pull requests and Webhooks</strong>.
</Trans>
);
break;

default:
helpText = (
<Trans t={t} ns="devconsole">
Use your Git Personal token. Create a token with repo, public_repo & admin:repo_hook
scopes and give your token an expiration, i.e 30d.
</Trans>
);
}

Comment on lines +63 to +101

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strong not b

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — replaced with <strong>. See commit 86ca033.

return <div data-test={testId}>{helpText}</div>;
};

const WebhookDocLinks = {
[GitProvider.GITHUB]:
'https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks',
Expand All @@ -61,17 +115,15 @@ const WebhookSection: FC<WebhoookSectionProps> = ({ pac, formContextField }) =>
const { values, setFieldValue } = useFormikContext<FormikValues>();
const fieldPrefix = formContextField ? `${formContextField}.` : '';
const { gitProvider, webhook } = _.get(values, formContextField) || values;
const [controllerUrl, setControllerUrl] = useState('');
const controllerUrl = useMemo(() => pac?.data?.['controller-url'] || '', [pac]);
const [webhookSecret, setWebhookSecret] = useState('');
const { t } = useTranslation('devconsole');

useEffect(() => {
const ctlUrl = pac?.data?.['controller-url'];
if (ctlUrl) {
setControllerUrl(ctlUrl);
setFieldValue(`${fieldPrefix}webhook.url`, ctlUrl);
if (controllerUrl) {
setFieldValue(`${fieldPrefix}webhook.url`, controllerUrl);
}
}, [fieldPrefix, pac, setFieldValue]);
}, [fieldPrefix, controllerUrl, setFieldValue]);

const autocompleteFilter = (text: string, item: any): boolean => fuzzy(text, item?.props?.name);

Expand Down Expand Up @@ -116,54 +168,6 @@ const WebhookSection: FC<WebhoookSectionProps> = ({ pac, formContextField }) =>
}
};

const HelpText = (): ReactElement => {
let helpText: ReactNode;
switch (gitProvider) {
case GitProvider.GITHUB:
helpText = (
<Trans t={t} ns="devconsole">
Use your GitHub Personal token. Use this{' '}
<ExternalLink href={AccessTokenDocLinks[GitProvider.GITHUB]}>link</ExternalLink> to
create a <b>classic</b> token with <b>repo</b> & <b>admin:repo_hook</b> scopes and give
your token an expiration, i.e 30d.
</Trans>
);
break;

case GitProvider.GITLAB:
helpText = (
<Trans t={t} ns="devconsole">
Use your Gitlab Personal access token. Use this{' '}
<ExternalLink href={AccessTokenDocLinks[GitProvider.GITLAB]}>link</ExternalLink> to
create a token with <b>api</b> scope. Select the role as <b>Maintainer/Owner</b>. Give
your token an expiration i.e 30d.
</Trans>
);
break;

case GitProvider.BITBUCKET:
helpText = (
<Trans t={t} ns="devconsole">
Use your Bitbucket App password. Use this{' '}
<ExternalLink href={AccessTokenDocLinks[GitProvider.BITBUCKET]}>link</ExternalLink> to
create a token with <b>Read and Write </b>scopes in{' '}
<b>Account, Workspace membership, Projects, Issues, Pull requests and Webhooks</b>.
</Trans>
);
break;

default:
helpText = (
<Trans t={t} ns="devconsole">
Use your Git Personal token. Create a token with repo, public_repo & admin:repo_hook
scopes and give your token an expiration, i.e 30d.
</Trans>
);
}

return <div data-test={`${values.gitProvider}-helptext`}>{helpText}</div>;
};

return (
<FormSection fullWidth={!fieldPrefix} extraMargin>
{gitProvider && gitProvider === GitProvider.BITBUCKET ? (
Expand Down Expand Up @@ -200,7 +204,12 @@ const WebhookSection: FC<WebhoookSectionProps> = ({ pac, formContextField }) =>
<InputField
name={`${fieldPrefix}webhook.token`}
type={TextInputTypes.text}
helpText={<HelpText />}
helpText={
<WebhookHelpText
gitProvider={gitProvider}
testId={`${values.gitProvider}-helptext`}
/>
}
required
/>
),
Expand Down
Loading