chore(ui-dev): migrate tslint to eslint (#11652) (#18079)

* feat(ui): setup eslint

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

* chore(ui): update `lint:fix` command

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

* fix(ui-lint): run `yarn lint:fix`

> solve remaining manually

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

* chore(ui): remove tslint from `ui`

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

* chore(docs-ui): add docs for VSCode configuration eslint

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

* chore(ui): prettier set `trailingComma` to `none`

> prettier config default is changed after version bump - https://prettier.io/docs/en/options.html#trailing-commas

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>

---------

Signed-off-by: Mayursinh Sarvaiya <marvinduff97@gmail.com>
This commit is contained in:
Mayursinh Sarvaiya
2024-05-08 12:30:58 -03:00
committed by GitHub
parent ef96dec5b2
commit 7945b26d95
40 changed files with 1670 additions and 332 deletions

View File

@@ -2,7 +2,7 @@
We use the following static code analysis tools:
* golangci-lint and tslint for compile time linting
* golangci-lint and eslint for compile time linting
* [codecov.io](https://codecov.io/gh/argoproj/argo-cd) - for code coverage
* [snyk.io](https://app.snyk.io/org/argoproj/projects) - for image scanning
* [sonarcloud.io](https://sonarcloud.io/organizations/argoproj/projects) - for code scans and security alerts

View File

@@ -6,5 +6,6 @@
"tabWidth": 4,
"jsxBracketSameLine": true,
"quoteProps": "consistent",
"arrowParens": "avoid"
"arrowParens": "avoid",
"trailingComma": "none"
}

View File

@@ -22,4 +22,25 @@ Make sure your code passes the lint checks:
```
yarn lint --fix
```
```
If you are using VSCode, add this configuration to `.vscode/settings.json` in the root of this repository to identify and fix lint issues automatically before you save file.
Install [Eslint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) in VSCode.
`.vscode/settings.json`
```json
{
"eslint.format.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
},
"eslint.workingDirectories": [
{
"directory": "./ui",
"!cwd": false
}
],
"eslint.experimental.useFlatConfig": true
}
```

37
ui/eslint.config.mjs Normal file
View File

@@ -0,0 +1,37 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginReactConfig from 'eslint-plugin-react/configs/recommended.js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
export default [
{languageOptions: {globals: globals.browser}},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-var-requires': 'off'
}
},
{
settings: {
react: {
version: 'detect'
}
},
...pluginReactConfig,
rules: {
'react/display-name': 'off',
'react/no-string-refs': 'off'
}
},
eslintPluginPrettierRecommended,
{
files: ['./src/**/*.{ts,tsx}']
},
{
ignores: ['dist', 'assets', '**/*.config.js', '__mocks__', 'coverage', '**/*.test.{ts,tsx}']
}
];

View File

@@ -6,8 +6,8 @@
"start": "webpack-dev-server --config ./src/app/webpack.config.js --mode development",
"docker": "./scripts/build_docker.sh",
"build": "find ./dist -type f -not -name gitkeep -delete && webpack --config ./src/app/webpack.config.js --mode production",
"lint": "tsc --noEmit --project ./src/app && tslint -p ./src/app",
"lint:fix": "tslint -p ./src/app --fix",
"lint": "tsc --noEmit --project ./src/app && eslint",
"lint:fix": "eslint --fix",
"test": "jest"
},
"dependencies": {
@@ -69,6 +69,7 @@
"@babel/preset-env": "^7.7.1",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.7.2",
"@eslint/js": "^9.1.1",
"@types/classnames": "^2.2.3",
"@types/cookie": "^0.5.1",
"@types/dagre": "^0.7.40",
@@ -96,6 +97,11 @@
"codecov": "^3.8.3",
"copy-webpack-plugin": "^6.1.1",
"esbuild-loader": "^2.18.0",
"eslint": "^9.1.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.1",
"globals": "^15.1.0",
"html-webpack-plugin": "^5.5.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.5.1",
@@ -103,7 +109,7 @@
"jest-transform-css": "^2.0.0",
"monaco-editor-webpack-plugin": "^7.0.0",
"postcss": "^8.4.38",
"prettier": "1.19",
"prettier": "^3.2.5",
"raw-loader": "^0.5.1",
"react-test-renderer": "16.8.3",
"sass": "^1.49.9",
@@ -112,11 +118,8 @@
"style-loader": "^0.20.1",
"ts-jest": "^27.1.3",
"ts-node": "10.9.1",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.0.1",
"tslint-react": "^5.0.0",
"typescript": "^4.9.5",
"typescript-eslint": "^7.8.0",
"webpack": "^5.84.1",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4",

View File

@@ -98,10 +98,7 @@ requests.onError.subscribe(async err => {
}
// Query for basehref and remove trailing /.
// If basehref is the default `/` it will become an empty string.
const basehref = document
.querySelector('head > base')
.getAttribute('href')
.replace(/\/$/, '');
const basehref = document.querySelector('head > base').getAttribute('href').replace(/\/$/, '');
if (isSSO) {
window.location.href = `${basehref}/auth/login?return_url=${encodeURIComponent(location.href)}`;
} else {

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-prototype-builtins */
import {AutocompleteField, Checkbox, DataLoader, DropDownMenu, FormField, HelpIcon, Select} from 'argo-ui';
import * as deepMerge from 'deepmerge';
import * as React from 'react';

View File

@@ -6,7 +6,6 @@ import {ResourceIcon} from '../resource-icon';
import {ResourceLabel} from '../resource-label';
import {ComparisonStatusIcon, HealthStatusIcon, nodeKey, createdOrNodeKey} from '../utils';
import {Consumer} from '../../../shared/context';
import * as _ from 'lodash';
import Moment from 'react-moment';
import {format} from 'date-fns';
import {ResourceNode, ResourceRef} from '../../../shared/models';

View File

@@ -250,8 +250,10 @@ export const ApplicationParameters = (props: {
if (params) {
for (const param of params) {
if (param.map && param.array) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
param.map = param.array.reduce((acc, {name, value}) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
acc[name] = value;
return acc;
@@ -322,6 +324,7 @@ function gatherDetails(
setAppParamsDeletedState: any
): EditablePanelItem[] {
const hasMultipleSources = app.spec.sources && app.spec.sources.length > 0;
// eslint-disable-next-line no-prototype-builtins
const isHelm = source.hasOwnProperty('chart');
if (hasMultipleSources) {
attributes.push({

View File

@@ -1,4 +1,4 @@
import { format, parse } from './kustomize-image';
import {format, parse} from './kustomize-image';
test('parse image version override', () => {
const image = parse('foo/bar:v1.0.0');
@@ -8,7 +8,7 @@ test('parse image version override', () => {
});
test('format image version override', () => {
const formatted = format({ name: 'foo/bar', newTag: 'v1.0.0' });
const formatted = format({name: 'foo/bar', newTag: 'v1.0.0'});
expect(formatted).toBe('foo/bar:v1.0.0');
});
@@ -21,7 +21,7 @@ test('parse image name override', () => {
});
test('format image name override', () => {
const formatted = format({ name: 'foo/bar', newTag: 'v1.0.0', newName: 'foo/bar1' });
const formatted = format({name: 'foo/bar', newTag: 'v1.0.0', newName: 'foo/bar1'});
expect(formatted).toBe('foo/bar=foo/bar1:v1.0.0');
});
@@ -33,6 +33,6 @@ test('parse image digest override', () => {
});
test('format image digest override', () => {
const formatted = format({ name: 'foo/bar', digest: 'sha:123' });
const formatted = format({name: 'foo/bar', digest: 'sha:123'});
expect(formatted).toBe('foo/bar@sha:123');
});

View File

@@ -145,9 +145,7 @@ export class PodView extends React.Component<PodViewProps> {
</Moment>
</div>
) : null}
{group.info?.map(infoItem => (
<div key={infoItem.name}>{infoItem.value}</div>
))}
{group.info?.map(infoItem => <div key={infoItem.name}>{infoItem.value}</div>)}
</div>
)}
</div>

View File

@@ -1,93 +1,109 @@
import {compareNodes, describeNode, ResourceTreeNode} from "./application-resource-tree";
import {compareNodes, describeNode, ResourceTreeNode} from './application-resource-tree';
test("describeNode.NoImages", () => {
expect(describeNode({
kind: "my-kind",
name: "my-name",
namespace: "my-ns",
} as ResourceTreeNode)).toBe(`Kind: my-kind
test('describeNode.NoImages', () => {
expect(
describeNode({
kind: 'my-kind',
name: 'my-name',
namespace: 'my-ns',
} as ResourceTreeNode),
).toBe(`Kind: my-kind
Namespace: my-ns
Name: my-name`)
Name: my-name`);
});
test("describeNode.Images", () => {
expect(describeNode({
kind: "my-kind",
name: "my-name",
namespace: "my-ns",
images: ['my-image:v1'],
} as ResourceTreeNode)).toBe(`Kind: my-kind
test('describeNode.Images', () => {
expect(
describeNode({
kind: 'my-kind',
name: 'my-name',
namespace: 'my-ns',
images: ['my-image:v1'],
} as ResourceTreeNode),
).toBe(`Kind: my-kind
Namespace: my-ns
Name: my-name
Images:
- my-image:v1`)
- my-image:v1`);
});
test("compareNodes", () => {
test('compareNodes', () => {
const nodes = [
{
resourceVersion: "1",
name: "a",
info: [{
"name": "Revision",
"value": "Rev:1"
}],
} as ResourceTreeNode,
{
orphaned: false,
resourceVersion: "1",
name: "a",
info: [{
"name": "Revision",
"value": "Rev:1"
}],
} as ResourceTreeNode,
{
orphaned: false,
resourceVersion: "1",
name: "b",
info: [{
"name": "Revision",
"value": "Rev:1"
}],
} as ResourceTreeNode,
{
orphaned: false,
resourceVersion: "2",
name: "a",
info: [{
"name": "Revision",
"value": "Rev:2"
}],
} as ResourceTreeNode,
{
orphaned: false,
resourceVersion: "2",
name: "b",
info: [{
"name": "Revision",
"value": "Rev:2"
}],
} as ResourceTreeNode,
{
orphaned: true,
resourceVersion: "1",
name: "a",
info: [{
"name": "Revision",
"value": "Rev:1"
}],
} as ResourceTreeNode,
{
resourceVersion: '1',
name: 'a',
info: [
{
name: 'Revision',
value: 'Rev:1',
},
],
} as ResourceTreeNode,
{
orphaned: false,
resourceVersion: '1',
name: 'a',
info: [
{
name: 'Revision',
value: 'Rev:1',
},
],
} as ResourceTreeNode,
{
orphaned: false,
resourceVersion: '1',
name: 'b',
info: [
{
name: 'Revision',
value: 'Rev:1',
},
],
} as ResourceTreeNode,
{
orphaned: false,
resourceVersion: '2',
name: 'a',
info: [
{
name: 'Revision',
value: 'Rev:2',
},
],
} as ResourceTreeNode,
{
orphaned: false,
resourceVersion: '2',
name: 'b',
info: [
{
name: 'Revision',
value: 'Rev:2',
},
],
} as ResourceTreeNode,
{
orphaned: true,
resourceVersion: '1',
name: 'a',
info: [
{
name: 'Revision',
value: 'Rev:1',
},
],
} as ResourceTreeNode,
];
expect(compareNodes(nodes[0], nodes[1])).toBe(0)
expect(compareNodes(nodes[2], nodes[1])).toBe(1)
expect(compareNodes(nodes[1], nodes[2])).toBe(-1)
expect(compareNodes(nodes[3], nodes[2])).toBe(-1)
expect(compareNodes(nodes[2], nodes[3])).toBe(1)
expect(compareNodes(nodes[4], nodes[3])).toBe(1)
expect(compareNodes(nodes[3], nodes[4])).toBe(-1)
expect(compareNodes(nodes[5], nodes[4])).toBe(1)
expect(compareNodes(nodes[4], nodes[5])).toBe(-1)
expect(compareNodes(nodes[0], nodes[4])).toBe(-1)
expect(compareNodes(nodes[4], nodes[0])).toBe(1)
expect(compareNodes(nodes[0], nodes[1])).toBe(0);
expect(compareNodes(nodes[2], nodes[1])).toBe(1);
expect(compareNodes(nodes[1], nodes[2])).toBe(-1);
expect(compareNodes(nodes[3], nodes[2])).toBe(-1);
expect(compareNodes(nodes[2], nodes[3])).toBe(1);
expect(compareNodes(nodes[4], nodes[3])).toBe(1);
expect(compareNodes(nodes[3], nodes[4])).toBe(-1);
expect(compareNodes(nodes[5], nodes[4])).toBe(1);
expect(compareNodes(nodes[4], nodes[5])).toBe(-1);
expect(compareNodes(nodes[0], nodes[4])).toBe(-1);
expect(compareNodes(nodes[4], nodes[0])).toBe(1);
});

View File

@@ -94,15 +94,7 @@ const NODE_TYPES = {
podGroup: 'pod_group'
};
// generate lots of colors with different darkness
const TRAFFIC_COLORS = [0, 0.25, 0.4, 0.6]
.map(darken =>
BASE_COLORS.map(item =>
color(item)
.darken(darken)
.hex()
)
)
.reduce((first, second) => first.concat(second), []);
const TRAFFIC_COLORS = [0, 0.25, 0.4, 0.6].map(darken => BASE_COLORS.map(item => color(item).darken(darken).hex())).reduce((first, second) => first.concat(second), []);
function getGraphSize(nodes: dagre.Node[]): {width: number; height: number} {
let width = 0;
@@ -892,7 +884,8 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
resourceVersion: props.app.metadata.resourceVersion,
group: 'argoproj.io',
version: '',
children: Array(),
// @ts-expect-error its not any
children: [],
status: props.app.status.sync.status,
health: props.app.status.health,
uid: props.app.kind + '-' + props.app.metadata.namespace + '-' + props.app.metadata.name,
@@ -1035,7 +1028,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
const loadBalancers = root.networkingInfo.ingress.map(ingress => ingress.hostname || ingress.ip);
const colorByService = new Map<string, string>();
(childrenByParentKey.get(treeNodeKey(root)) || []).forEach((child, i) => colorByService.set(treeNodeKey(child), TRAFFIC_COLORS[i % TRAFFIC_COLORS.length]));
(childrenByParentKey.get(treeNodeKey(root)) || []).sort(compareNodes).forEach((child, i) => {
(childrenByParentKey.get(treeNodeKey(root)) || []).sort(compareNodes).forEach(child => {
processNode(child, root, [colorByService.get(treeNodeKey(child))]);
});
if (root.podGroup && props.showCompactNodes) {

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-prototype-builtins */
import * as React from 'react';
import {FormApi, NestedForm, Text, Form} from 'react-form';
import {Checkbox, FormField} from 'argo-ui';
@@ -7,6 +8,7 @@ import * as models from '../../../shared/models';
import './application-retry-options.scss';
// eslint-disable-next-line no-useless-escape
const durationRegex = /^([\d\.]+[HMS])+$/i;
const durationRegexError = 'Should be 1h10m10s/10h10m/10m/10s';

View File

@@ -21,6 +21,6 @@ const retryOptionsView: Array<(initData: models.RetryStrategy) => React.ReactNod
];
export const ApplicationRetryView = ({initValues}: {initValues?: models.RetryStrategy}) => {
const result = !initValues ? 'Retry disabled' : retryOptionsView.map((render, i) => render(initValues));
const result = !initValues ? 'Retry disabled' : retryOptionsView.map(render => render(initValues));
return <div className='application-retry-option-view-list'>{result}</div>;
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-prototype-builtins */
import {AutocompleteField, DropDownMenu, ErrorNotification, FormField, FormSelect, HelpIcon, NotificationType} from 'argo-ui';
import * as React from 'react';
import {FormApi, Text} from 'react-form';

View File

@@ -10,6 +10,7 @@ import './edit-notification-subscriptions.scss';
export const NOTIFICATION_SUBSCRIPTION_ANNOTATION_PREFIX = 'notifications.argoproj.io/subscribe';
// eslint-disable-next-line no-useless-escape
export const NOTIFICATION_SUBSCRIPTION_ANNOTATION_REGEX = new RegExp(`^notifications\.argoproj\.io\/subscribe\.[a-zA-Z-]{1,100}\.[a-zA-Z-]{1,100}$`);
export type TNotificationSubscription = {
@@ -96,20 +97,22 @@ export const useEditNotificationSubscriptions = (annotations: models.Application
const onRemoveSubscription = (idx: number) => idx >= 0 && setSubscriptions(subscriptions.filter((_, i) => i !== idx));
const withNotificationSubscriptions = (updateApp: ApplicationSummaryProps['updateApp']) => (...args: Parameters<ApplicationSummaryProps['updateApp']>) => {
const app = args[0];
const withNotificationSubscriptions =
(updateApp: ApplicationSummaryProps['updateApp']) =>
(...args: Parameters<ApplicationSummaryProps['updateApp']>) => {
const app = args[0];
const notificationSubscriptionsRaw = notificationSubscriptionsParser.subscriptionsToAnnotations(subscriptions);
const notificationSubscriptionsRaw = notificationSubscriptionsParser.subscriptionsToAnnotations(subscriptions);
if (Object.keys(notificationSubscriptionsRaw)?.length) {
app.metadata.annotations = {
...notificationSubscriptionsRaw,
...(app.metadata.annotations || {})
};
}
if (Object.keys(notificationSubscriptionsRaw)?.length) {
app.metadata.annotations = {
...notificationSubscriptionsRaw,
...(app.metadata.annotations || {})
};
}
return updateApp(app, args[1]);
};
return updateApp(app, args[1]);
};
const onResetNotificationSubscriptions = () => setSubscriptions(notificationSubscriptionsParser.annotationsToSubscriptions(annotations));

View File

@@ -1,4 +1,4 @@
import { ExternalLink, ExternalLinks, InvalidExternalLinkError } from './application-urls';
import {ExternalLink, ExternalLinks, InvalidExternalLinkError} from './application-urls';
test('rejects malicious URLs', () => {
expect(() => {
@@ -29,24 +29,19 @@ test('allows relative URLs', () => {
expect(new ExternalLink('/applications').ref).toEqual('/applications');
});
test('URLs format', () => {
expect(new ExternalLink('https://localhost:8080/applications')).toEqual({
ref: 'https://localhost:8080/applications',
title: 'https://localhost:8080/applications',
})
});
expect(new ExternalLink('title|https://localhost:8080/applications')).toEqual({
ref: 'https://localhost:8080/applications',
title: 'title',
})
});
});
test('malicious URLs from list to be removed', () => {
const urls: string[] = [
'javascript:alert("hi")',
'https://localhost:8080/applications',
]
const urls: string[] = ['javascript:alert("hi")', 'https://localhost:8080/applications'];
const links = ExternalLinks(urls);
expect(links).toHaveLength(1);
@@ -56,16 +51,8 @@ test('malicious URLs from list to be removed', () => {
});
});
test('list to be sorted', () => {
const urls: string[] = [
'https://a',
'https://b',
'a|https://c',
'z|https://c',
'x|https://d',
'x|https://c',
]
const urls: string[] = ['https://a', 'https://b', 'a|https://c', 'z|https://c', 'x|https://d', 'x|https://c'];
const links = ExternalLinks(urls);
// 'a|https://c',
@@ -75,12 +62,12 @@ test('list to be sorted', () => {
// 'https://a',
// 'https://b',
expect(links).toHaveLength(6);
expect(links[0].title).toEqual('a')
expect(links[1].title).toEqual('x')
expect(links[1].ref).toEqual('https://c')
expect(links[2].title).toEqual('x')
expect(links[2].ref).toEqual('https://d')
expect(links[3].title).toEqual('z')
expect(links[4].title).toEqual('https://a')
expect(links[5].title).toEqual('https://b')
expect(links[0].title).toEqual('a');
expect(links[1].title).toEqual('x');
expect(links[1].ref).toEqual('https://c');
expect(links[2].title).toEqual('x');
expect(links[2].ref).toEqual('https://d');
expect(links[3].title).toEqual('z');
expect(links[4].title).toEqual('https://a');
expect(links[5].title).toEqual('https://b');
});

View File

@@ -53,7 +53,7 @@ export const ApplicationsStatusBar = ({applications}: ApplicationsStatusBarProps
return (
<Consumer>
{ctx => (
{() => (
<>
{totalItems > 1 && (
<div className='status-bar'>

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-prototype-builtins */
type operatorFn = (labels: {[name: string]: string}, key: string, values: string[]) => boolean;
const operators: {[type: string]: operatorFn} = {

View File

@@ -94,6 +94,7 @@ export const PodsLogsViewer = (props: PodLogsProps) => {
useEffect(() => {
// https://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
// matchNothing this is chosen instead of empty regexp, because that would match everything and break colored logs
// eslint-disable-next-line no-useless-escape
setHighlight(filter === '' ? matchNothing : new RegExp(filter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'));
}, [filter]);

View File

@@ -373,7 +373,7 @@ async function getSources(app: models.Application) {
const length = sources.length;
for (let i = 0; i < length; i++) {
const aSource = sources[i];
const repoDetail = await services.repos.appDetails(aSource, app.metadata.name, app.spec.project).catch(e => ({
const repoDetail = await services.repos.appDetails(aSource, app.metadata.name, app.spec.project).catch(() => ({
type: 'Directory' as AppSourceType,
path: aSource.path
}));

View File

@@ -22,7 +22,7 @@ test('getAppOperationState.Operation', () => {
test('getAppOperationState.Status', () => {
const state = getAppOperationState({
metadata: {},
status: {operationState: {phase: OperationPhases.Error, startedAt: zero}}
status: {operationState: {phase: OperationPhases.Error, startedAt: zero}},
} as Application);
expect(state.phase).toBe(OperationPhases.Error);
@@ -188,10 +188,10 @@ test('ResourceResultIcon.Hook.Running', () => {
{
hookType: 'Sync',
hookPhase: OperationPhases.Running,
message: 'my-message'
message: 'my-message',
} as ResourceResult
}
/>
/>,
)
.toJSON();

View File

@@ -245,6 +245,7 @@ export const ComparisonStatusIcon = ({
title = 'Synced';
break;
case appModels.SyncStatuses.OutOfSync:
// eslint-disable-next-line no-case-declarations
const requiresPruning = resource && resource.requiresPruning;
className = requiresPruning ? 'fa fa-trash' : 'fa fa-arrow-alt-circle-up';
title = 'OutOfSync';
@@ -433,7 +434,7 @@ function getResourceActionsMenuItems(resource: ResourceTreeNode, metadata: model
});
}
}
} as MenuItem)
}) as MenuItem
);
})
.catch(() => [] as MenuItem[]);
@@ -514,7 +515,7 @@ function getActionItems(
iconClassName: `fa fa-fw ${link.iconClass ? link.iconClass : 'fa-external-link'}`,
action: () => window.open(link.url, '_blank'),
tooltip: link.description
} as MenuItem)
}) as MenuItem
);
})
.catch(() => [] as MenuItem[]);
@@ -610,8 +611,7 @@ export function renderResourceButtons(
apis: ContextApis,
appChanged: BehaviorSubject<appModels.Application>
): React.ReactNode {
let menuItems: Observable<ActionMenuItem[]>;
menuItems = getActionItems(resource, application, tree, apis, appChanged, true);
const menuItems: Observable<ActionMenuItem[]> = getActionItems(resource, application, tree, apis, appChanged, true);
return (
<DataLoader load={() => menuItems}>
{items => (
@@ -628,12 +628,7 @@ export function renderResourceButtons(
}
}}
icon={item.iconClassName}
tooltip={
item.title
.toString()
.charAt(0)
.toUpperCase() + item.title.toString().slice(1)
}
tooltip={item.title.toString().charAt(0).toUpperCase() + item.title.toString().slice(1)}
/>
))}
</div>
@@ -1250,14 +1245,8 @@ export function appInstanceName(app: appModels.Application): string {
}
export function formatCreationTimestamp(creationTimestamp: string) {
const createdAt = moment
.utc(creationTimestamp)
.local()
.format('MM/DD/YYYY HH:mm:ss');
const fromNow = moment
.utc(creationTimestamp)
.local()
.fromNow();
const createdAt = moment.utc(creationTimestamp).local().format('MM/DD/YYYY HH:mm:ss');
const fromNow = moment.utc(creationTimestamp).local().fromNow();
return (
<span>
{createdAt}

View File

@@ -219,7 +219,7 @@ export class CertsList extends React.Component<RouteComponentProps<any>> {
let knownHostEntries: models.RepoCert[] = [];
atob(params.certData)
.split('\n')
.forEach(function processEntry(item, index) {
.forEach(function processEntry(item) {
const trimmedLine = item.trimLeft();
if (trimmedLine.startsWith('#') === false) {
const knownHosts = trimmedLine.split(' ', 3);
@@ -227,6 +227,7 @@ export class CertsList extends React.Component<RouteComponentProps<any>> {
// Perform a little sanity check on the data - server
// checks too, but let's not send it invalid data in
// the first place.
// eslint-disable-next-line no-useless-escape
const subType = knownHosts[1].match(/^(ssh\-[a-z0-9]+|ecdsa-[a-z0-9\-]+)$/gi);
if (subType != null) {
// Key could be valid for multiple hosts

View File

@@ -1,7 +1,6 @@
import {DropDownMenu, ErrorNotification, NotificationType} from 'argo-ui';
import {Tooltip, Toolbar} from 'argo-ui';
import * as React from 'react';
import {RouteComponentProps} from 'react-router-dom';
import {clusterName, ConnectionStateIcon, DataLoader, EmptyState, Page} from '../../../shared/components';
import {Consumer, Context} from '../../../shared/context';
import * as models from '../../../shared/models';
@@ -46,7 +45,7 @@ const CustomTopBar = (props: {toolbar?: Toolbar | Observable<Toolbar>}) => {
);
};
export const ClustersList = (props: RouteComponentProps<{}>) => {
export const ClustersList = () => {
const clustersLoaderRef = React.useRef<DataLoader>();
return (
<Consumer>

View File

@@ -574,7 +574,7 @@ export class ProjectDetails extends React.Component<RouteComponentProps<{name: s
{
title: 'NAME',
view: proj.metadata.name,
edit: (_: FormApi) => proj.metadata.name
edit: () => proj.metadata.name
},
{
title: 'DESCRIPTION',

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-case-declarations */
import {AutocompleteField, DropDownMenu, FormField, FormSelect, HelpIcon, NotificationType, SlidingPanel, Tooltip} from 'argo-ui';
import * as PropTypes from 'prop-types';
import * as React from 'react';

View File

@@ -54,14 +54,14 @@ export const BadgePanel = ({app, project, appNamespace, nsEnabled}: {app?: strin
badgeType === 'URL'
? badgeURL
: badgeType === 'Markdown'
? `[![${alt}](${badgeURL})](${entityURL})`
: badgeType === 'Textile'
? `!${badgeURL}!:${entityURL}`
: badgeType === 'Rdoc'
? `{<img src="${badgeURL}" alt="${alt}" />}[${entityURL}]`
: badgeType === 'AsciiDoc'
? `image:${badgeURL}["${alt}", link="${entityURL}"]`
: ''
? `[![${alt}](${badgeURL})](${entityURL})`
: badgeType === 'Textile'
? `!${badgeURL}!:${entityURL}`
: badgeType === 'Rdoc'
? `{<img src="${badgeURL}" alt="${alt}" />}[${entityURL}]`
: badgeType === 'AsciiDoc'
? `image:${badgeURL}["${alt}", link="${entityURL}"]`
: ''
}
/>
</div>

View File

@@ -52,7 +52,7 @@ export class EditablePanel<T = {}> extends React.Component<EditablePanelProps<T>
public UNSAFE_componentWillReceiveProps(nextProps: EditablePanelProps<T>) {
if (this.formApi && JSON.stringify(this.props.values) !== JSON.stringify(nextProps.values)) {
if (!!nextProps.noReadonlyMode) {
if (nextProps.noReadonlyMode) {
this.formApi.setAllValues(nextProps.values);
}
}

View File

@@ -6,7 +6,7 @@ export class ErrorBoundary extends React.Component<{message?: string}, {hasError
this.state = {hasError: false};
}
static getDerivedStateFromError(error: React.ErrorInfo) {
static getDerivedStateFromError() {
return {hasError: true};
}

View File

@@ -54,7 +54,7 @@ export const Page = (props: PageProps) => {
return (
<DataLoader load={() => services.viewPreferences.getPreferences()}>
{pref => (
<div className={`${props.hideAuth ? 'page-wrapper' : ''} ${!!pref.hideSidebar ? 'sb-page-wrapper__sidebar-collapsed' : 'sb-page-wrapper'}`}>
<div className={`${props.hideAuth ? 'page-wrapper' : ''} ${pref.hideSidebar ? 'sb-page-wrapper__sidebar-collapsed' : 'sb-page-wrapper'}`}>
<ArgoPage
title={props.title}
children={props.children}

View File

@@ -3,22 +3,19 @@ import * as React from 'react';
import {ProgressPopup} from './progress-popup';
test('ProgressPopup.0%', () => {
const state = renderer.create(<ProgressPopup onClose={() => {
}} percentage={0} title={''}/>);
const state = renderer.create(<ProgressPopup onClose={() => {}} percentage={0} title={''} />);
expect(state).toMatchSnapshot();
});
test('ProgressPopup.50%', () => {
const state = renderer.create(<ProgressPopup onClose={() => {
}} percentage={50} title={'My Title'}/>);
const state = renderer.create(<ProgressPopup onClose={() => {}} percentage={50} title={'My Title'} />);
expect(state).toMatchSnapshot();
});
test('ProgressPopup.100%', () => {
const state = renderer.create(<ProgressPopup onClose={() => {
}} percentage={100} title={''}/>);
const state = renderer.create(<ProgressPopup onClose={() => {}} percentage={100} title={''} />);
expect(state).toMatchSnapshot();
});

View File

@@ -1,33 +1,44 @@
import * as renderer from 'react-test-renderer';
import * as React from 'react';
import {isSHA, Revision} from "./revision";
import {isSHA, Revision} from './revision';
test('Revision.SHA1.Children', () => {
const tree = renderer.create(<Revision repoUrl='http://github.com/my-org/my-repo' revision='24eb0b24099b2e9afff72558724e88125eaa0176'>foo</Revision>).toJSON();
const tree = renderer
.create(
<Revision repoUrl='http://github.com/my-org/my-repo' revision='24eb0b24099b2e9afff72558724e88125eaa0176'>
foo
</Revision>,
)
.toJSON();
expect(tree).toMatchSnapshot()
expect(tree).toMatchSnapshot();
});
test('Revision.SHA1.NoChildren', () => {
const tree = renderer.create(<Revision repoUrl='http://github.com/my-org/my-repo' revision='24eb0b24099b2e9afff72558724e88125eaa0176'/>).toJSON();
const tree = renderer.create(<Revision repoUrl='http://github.com/my-org/my-repo' revision='24eb0b24099b2e9afff72558724e88125eaa0176' />).toJSON();
expect(tree).toMatchSnapshot()
expect(tree).toMatchSnapshot();
});
test('Revision.Branch.Children', () => {
const tree = renderer.create(<Revision repoUrl='http://github.com/my-org/my-repo' revision='long-branch-name'>foo</Revision>).toJSON();
const tree = renderer
.create(
<Revision repoUrl='http://github.com/my-org/my-repo' revision='long-branch-name'>
foo
</Revision>,
)
.toJSON();
expect(tree).toMatchSnapshot()
expect(tree).toMatchSnapshot();
});
test('Revision.Branch.NoChildren', () => {
const tree = renderer.create(<Revision repoUrl='http://github.com/my-org/my-repo' revision='long-branch-name'/>).toJSON();
const tree = renderer.create(<Revision repoUrl='http://github.com/my-org/my-repo' revision='long-branch-name' />).toJSON();
expect(tree).toMatchSnapshot()
expect(tree).toMatchSnapshot();
});
test('isSHA1', () => {
expect(isSHA('24eb0b24099b2e9afff72558724e88125eaa0176')).toBe(true);
expect(isSHA('master')).toBe(false);
});
});

View File

@@ -16,7 +16,8 @@ test('github.com', () => {
'git@github.com:argoproj/argo-cd.git',
'024dee09f543ce7bb5af7ca50260504d89dfda94',
'https://github.com/argoproj/argo-cd',
'https://github.com/argoproj/argo-cd/commit/024dee09f543ce7bb5af7ca50260504d89dfda94');
'https://github.com/argoproj/argo-cd/commit/024dee09f543ce7bb5af7ca50260504d89dfda94',
);
});
// for enterprise github installations
@@ -26,7 +27,8 @@ test('github.my-enterprise.com', () => {
'git@github.my-enterprise.com:my-org/my-repo.git',
'a06f2be80a4da89abb8ced904beab75b3ec6db0e',
'https://github.my-enterprise.com/my-org/my-repo',
'https://github.my-enterprise.com/my-org/my-repo/commit/a06f2be80a4da89abb8ced904beab75b3ec6db0e');
'https://github.my-enterprise.com/my-org/my-repo/commit/a06f2be80a4da89abb8ced904beab75b3ec6db0e',
);
});
test('gitlab.com', () => {
@@ -35,7 +37,8 @@ test('gitlab.com', () => {
'git@gitlab.com:alex_collins/private-repo.git',
'b1fe9426ead684d7af16958920968342ee295c1f',
'https://gitlab.com/alex_collins/private-repo',
'https://gitlab.com/alex_collins/private-repo/-/commit/b1fe9426ead684d7af16958920968342ee295c1f');
'https://gitlab.com/alex_collins/private-repo/-/commit/b1fe9426ead684d7af16958920968342ee295c1f',
);
});
test('bitbucket.org', () => {
@@ -44,7 +47,8 @@ test('bitbucket.org', () => {
'git@bitbucket.org:alexcollinsinuit/test-repo.git',
'38fb93957deb45ff546af13399a92ac0d568c350',
'https://bitbucket.org/alexcollinsinuit/test-repo',
'https://bitbucket.org/alexcollinsinuit/test-repo/commits/38fb93957deb45ff546af13399a92ac0d568c350');
'https://bitbucket.org/alexcollinsinuit/test-repo/commits/38fb93957deb45ff546af13399a92ac0d568c350',
);
});
test('empty url', () => {

View File

@@ -12,6 +12,6 @@ export interface ContextApis {
baseHref: string;
}
export const Context = React.createContext<ContextApis & {history: History}>(null);
export let {Provider, Consumer} = Context;
export const {Provider, Consumer} = Context;
export const AuthSettingsCtx = React.createContext<models.AuthSettings>(null);

View File

@@ -3,11 +3,11 @@ import {concatMaps} from './utils';
test('map concatenation', () => {
const map1 = {
a: '1',
b: '2'
b: '2',
};
const map2 = {
a: '9',
c: '8'
c: '8',
};
const map3 = concatMaps(map1, map2);
expect(map3).toEqual(new Map(Object.entries({a: '9', b: '2', c: '8'})));

View File

@@ -1,4 +1,4 @@
declare var SYSTEM_INFO: { version: string; };
declare let SYSTEM_INFO: {version: string};
// suppress TS7016: Could not find a declaration file for module
declare module 'react-diff-view';
declare module 'unidiff';
declare module 'unidiff';

View File

@@ -1,21 +0,0 @@
{
"extends": [
"tslint:recommended", "tslint-react", "tslint-plugin-prettier", "tslint-config-prettier"
],
"jsRules": {},
"rules": {
"prettier": true,
"quotemark": [true, "single"],
"no-var-requires": false,
"interface-name": false,
"jsx-no-multiline-js": false,
"object-literal-sort-keys": false,
"jsx-alignment": false,
"max-line-length": [true, 200],
"jsx-no-lambda": false,
"array-type": false,
"max-classes-per-file": false,
"newline-per-chained-call": false
},
"rulesDirectory": []
}

File diff suppressed because it is too large Load Diff