[UI] fix eslint/prettier warnings (#678)
* UI spring cleaning - fix ~570 outstanding eslint warnings - make eslint force user to correct warnings - remove .css files that are not referenced - remove dummy.tsx as it is not consumed anywhere - remove a few functions that were "dead code" (ie. not consumed anywhere) - remove commented out blocks of code that had no explanatory comments surrounding them * add prettier to pre-commit configuration * change ignoreRestSiblings to true we have a few spots in the codebase where we destructure an object key and then use something like ...restProps setting this to true allows that * upgrade from eslint 7.21.0 to 8.25.0 - add @grafana/eslint-config to dev dependencies and pre-commit eslint deps - add @grafana/eslint-config peer dependencies to package.json * fix remaining outstanding prettier warnings * enable noUnusedLocals and noUnusedParameters and fix errors related to this * make pre-commit complain about eslint warnings * import from moment-timezone instead of moment * fix react/display-name eslint warning * add eslint-plugin-react-hooks to dev deps this is a peer dependency from @grafana/eslint-config * turn off react/prop-types * temporarily turn off react-hooks/exhaustive-deps add note that it will be turned back on and fixed in next PR * fix unused import errors after rebase to dev * fix more new prettier errors * turn react/no-unescaped-entities eslint rule off * address PR comment about useReducer * remove includeTemplateGroup from src/components/AlertTemplates/AlertTemplatesForm.helper.tsx * update arg typing for refreshPageError * update handleSyncException typing * fix strict equality in containers/IntegrationSettings/parts/Autoresolve.tsx * enhance typing in components/AlertTemplates/AlertTemplatesForm.tsx * revert small change per Maxim's comment
This commit is contained in:
parent
a60e14c3c6
commit
6e5cb4e8a7
182 changed files with 822 additions and 1468 deletions
|
|
@ -23,16 +23,26 @@ repos:
|
|||
- flake8-tidy-imports
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||
rev: v7.21.0
|
||||
rev: v8.25.0
|
||||
hooks:
|
||||
- id: eslint
|
||||
entry: bash -c 'cd grafana-plugin && eslint --fix ${@/grafana-plugin\//}' --
|
||||
entry: bash -c 'cd grafana-plugin && eslint --max-warnings=0 --fix ${@/grafana-plugin\//}' --
|
||||
types: [file]
|
||||
files: ^grafana-plugin/src/.*\.(js|jsx|ts|tsx)$
|
||||
additional_dependencies:
|
||||
- eslint@7.21.0
|
||||
- eslint@^8.25.0
|
||||
- eslint-plugin-import@^2.25.4
|
||||
- eslint-plugin-rulesdir@^0.2.1
|
||||
- "@grafana/eslint-config@^5.0.0"
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: "v2.7.1"
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [css, javascript, jsx, ts, tsx, json]
|
||||
files: ^grafana-plugin/src
|
||||
additional_dependencies:
|
||||
- prettier@^2.7.1
|
||||
|
||||
- repo: https://github.com/thibaudcolas/pre-commit-stylelint
|
||||
rev: v13.13.1
|
||||
|
|
@ -43,4 +53,6 @@ repos:
|
|||
files: ^grafana-plugin/src/.*\.css$
|
||||
additional_dependencies:
|
||||
- stylelint@^13.13.1
|
||||
- stylelint-prettier@^2.0.0
|
||||
- stylelint-config-standard@^22.0.0
|
||||
- stylelint-config-prettier@^9.0.3
|
||||
|
|
|
|||
|
|
@ -9,18 +9,7 @@ module.exports = {
|
|||
'^assets|^components|^containers|^declare|^icons|^img|^interceptors|^models|^network|^pages|^services|^state|^utils',
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['warn', { vars: 'all', args: 'after-used', ignoreRestSiblings: false }],
|
||||
'react/prop-types': 'warn',
|
||||
'react/display-name': 'warn',
|
||||
'react/jsx-key': 'warn',
|
||||
'react-hooks/exhaustive-deps': 'off',
|
||||
'react/no-unescaped-entities': 'warn',
|
||||
'react/jsx-no-target-blank': 'warn',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
'no-restricted-imports': 'warn',
|
||||
eqeqeq: 'warn',
|
||||
'no-duplicate-imports': 'error',
|
||||
'rulesdir/no-relative-import-paths': ['error', { allowSameFolder: true }],
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
|
|
@ -47,5 +36,33 @@ module.exports = {
|
|||
'newlines-between': 'always',
|
||||
},
|
||||
],
|
||||
'no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-restricted-imports': 'warn',
|
||||
'react/display-name': 'warn',
|
||||
/**
|
||||
* It appears as though the react/prop-types rule has a bug in it
|
||||
* when your props extend an interface
|
||||
* https://github.com/jsx-eslint/eslint-plugin-react/issues/3325
|
||||
*/
|
||||
'react/prop-types': 'off',
|
||||
'react/jsx-key': 'warn',
|
||||
'react/jsx-no-target-blank': 'warn',
|
||||
'react/no-unescaped-entities': 'off',
|
||||
/**
|
||||
* TODO: react-hooks/exhaustive-deps is temporarily disabled
|
||||
* this will be turned back on, and the warnings fixed, in a forthcoming PR
|
||||
*/
|
||||
'react-hooks/exhaustive-deps': 'off',
|
||||
'rulesdir/no-relative-import-paths': ['error', { allowSameFolder: true }],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
{
|
||||
"extends": "stylelint-config-standard",
|
||||
"rules": {
|
||||
"block-no-empty": [true,{ "severity": "warning"}],
|
||||
"extends": ["stylelint-config-standard", "stylelint-prettier/recommended"],
|
||||
"plugins": ["stylelint-prettier"],
|
||||
"rules": {
|
||||
"block-no-empty": [true, { "severity": "warning" }],
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoClasses": ["global"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"prettier/prettier": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
"version": "1.0.0",
|
||||
"description": "Grafana OnCall Plugin",
|
||||
"scripts": {
|
||||
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./src",
|
||||
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --quiet ./src",
|
||||
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --max-warnings=0 ./src",
|
||||
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --max-warnings=0 --quiet ./src",
|
||||
"stylelint": "stylelint ./src/**/*.css",
|
||||
"stylelint:fix": "stylelint --fix ./src/**/*.css",
|
||||
"build": "grafana-toolkit plugin:build",
|
||||
|
|
@ -52,6 +52,7 @@
|
|||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@grafana/data": "^9.1.1",
|
||||
"@grafana/eslint-config": "^5.0.0",
|
||||
"@grafana/runtime": "^9.1.1",
|
||||
"@grafana/toolkit": "^9.1.1",
|
||||
"@grafana/ui": "^9.1.1",
|
||||
|
|
@ -67,8 +68,13 @@
|
|||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-test-renderer": "^17.0.2",
|
||||
"@types/throttle-debounce": "^5.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"dompurify": "^2.3.12",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-plugin-jsdoc": "^39.3.14",
|
||||
"eslint-plugin-react": "^7.31.10",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-rulesdir": "^0.2.1",
|
||||
"jest": "^27.5.1",
|
||||
"jest-environment-jsdom": "^27.5.1",
|
||||
|
|
@ -78,8 +84,11 @@
|
|||
"plop": "^2.7.4",
|
||||
"postcss-loader": "^7.0.1",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-prettier": "^2.0.0",
|
||||
"ts-jest": "^27.1.3",
|
||||
"ts-loader": "^9.3.1",
|
||||
"typescript": "4.6.4",
|
||||
"webpack-bundle-analyzer": "^4.6.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useMemo } from 'react';
|
||||
|
||||
import { AppRootProps } from '@grafana/data';
|
||||
import { Button, HorizontalGroup, LinkButton, VerticalGroup } from '@grafana/ui';
|
||||
import { Button, HorizontalGroup, LinkButton } from '@grafana/ui';
|
||||
import dayjs from 'dayjs';
|
||||
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
||||
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
|
||||
|
|
@ -30,8 +30,6 @@ dayjs.extend(isSameOrBefore);
|
|||
dayjs.extend(isSameOrAfter);
|
||||
dayjs.extend(isoWeek);
|
||||
|
||||
// dayjs().weekday(0);
|
||||
|
||||
import './style/vars.css';
|
||||
import './style/index.css';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
Developer-Friendly
|
||||
Alert Management
|
||||
with Brilliant Slack Integration
|
||||
|
||||
- Connect monitoring systems
|
||||
- Collect and analyze data
|
||||
- On-call rotation
|
||||
|
|
@ -10,5 +11,6 @@ with Brilliant Slack Integration
|
|||
- Never miss alerts with calls and SMS
|
||||
|
||||
## Documentation
|
||||
|
||||
- [On Github](http://github.com/grafana/oncall)
|
||||
- [Grafana OnCall](https://grafana.com/docs/grafana-cloud/oncall/)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,3 @@ export function getLabelFromTemplateName(templateName: string, group: any) {
|
|||
}
|
||||
return arrayWithNeededValues.join(' ');
|
||||
}
|
||||
|
||||
export function includeTemplateGroup(groupName: string) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import cn from 'classnames/bind';
|
|||
import { omit } from 'lodash-es';
|
||||
|
||||
import { templatesToRender, Template } from 'components/AlertTemplates/AlertTemplatesForm.config';
|
||||
import { getLabelFromTemplateName, includeTemplateGroup } from 'components/AlertTemplates/AlertTemplatesForm.helper';
|
||||
import Collapse from 'components/Collapse/Collapse';
|
||||
import { getLabelFromTemplateName } from 'components/AlertTemplates/AlertTemplatesForm.helper';
|
||||
import Block from 'components/GBlock/Block';
|
||||
import MonacoJinja2Editor from 'components/MonacoJinja2Editor/MonacoJinja2Editor';
|
||||
import SourceCode from 'components/SourceCode/SourceCode';
|
||||
|
|
@ -41,7 +40,6 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
|
|||
const {
|
||||
onUpdateTemplates,
|
||||
templates,
|
||||
errors,
|
||||
alertReceiveChannelId,
|
||||
alertGroupId,
|
||||
demoAlertEnabled,
|
||||
|
|
@ -53,6 +51,8 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
|
|||
const [tempValues, setTempValues] = useState<{
|
||||
[key: string]: string | null;
|
||||
}>({});
|
||||
const [activeGroup, setActiveGroup] = useState<string>();
|
||||
const [activeTemplate, setActiveTemplate] = useState<Template>();
|
||||
|
||||
useEffect(() => {
|
||||
makeRequest('/preview_template_options/', {});
|
||||
|
|
@ -80,14 +80,11 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
|
|||
const handleReset = () => {
|
||||
const temValuesCopy = omit(
|
||||
tempValues,
|
||||
groups[activeGroup].map((group: any) => group.name)
|
||||
groups[activeGroup].map((group) => group.name)
|
||||
);
|
||||
setTempValues(temValuesCopy);
|
||||
};
|
||||
|
||||
const [activeGroup, setActiveGroup] = useState<string>();
|
||||
const [activeTemplate, setActiveTemplate] = useState<any>();
|
||||
|
||||
const filteredTemplatesToRender = useMemo(() => {
|
||||
return templates
|
||||
? templatesToRender.filter((template) => {
|
||||
|
|
@ -97,13 +94,10 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
|
|||
}, [templates]);
|
||||
|
||||
const groups = useMemo(() => {
|
||||
const groups: { [key: string]: any } = {};
|
||||
const groups: { [key: string]: Template[] } = {};
|
||||
|
||||
filteredTemplatesToRender.forEach((templateToRender) => {
|
||||
if (!groups[templateToRender.group]) {
|
||||
if (!includeTemplateGroup(templateToRender.group)) {
|
||||
return;
|
||||
}
|
||||
groups[templateToRender.group] = [];
|
||||
}
|
||||
groups[templateToRender.group].push(templateToRender);
|
||||
|
|
@ -113,11 +107,7 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
|
|||
|
||||
const getGroupByTemplateName = (templateName: string) => {
|
||||
Object.values(groups).find((group) => {
|
||||
const foundTemplate = group.find((obj: any) => {
|
||||
if (obj.name == templateName) {
|
||||
return obj;
|
||||
}
|
||||
});
|
||||
const foundTemplate = group.find((obj) => obj.name === templateName);
|
||||
setActiveGroup(foundTemplate?.group);
|
||||
});
|
||||
};
|
||||
|
|
@ -210,18 +200,18 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
|
|||
suggestions
|
||||
</p>
|
||||
</Text>
|
||||
{groups[activeGroup].map((activeTemplate: any) => (
|
||||
{groups[activeGroup].map((activeTemplate) => (
|
||||
<div
|
||||
key={activeTemplate.name}
|
||||
className={cx('template-form', {
|
||||
'template-form-full': true,
|
||||
'autoresolve-condition': selectedTemplateName && activeTemplate.name == 'resolve_condition_template',
|
||||
'autoresolve-condition': selectedTemplateName && activeTemplate.name === 'resolve_condition_template',
|
||||
})}
|
||||
>
|
||||
<Label className={cx({ 'autoresolve-label': activeTemplate.name == 'resolve_condition_template' })}>
|
||||
<Label className={cx({ 'autoresolve-label': activeTemplate.name === 'resolve_condition_template' })}>
|
||||
{getLabelFromTemplateName(activeTemplate.name, activeGroup)}
|
||||
</Label>
|
||||
{activeTemplate.name == 'resolve_condition_template' && (
|
||||
{activeTemplate.name === 'resolve_condition_template' && (
|
||||
<Text type="secondary" size="small">
|
||||
To activate autoresolving change integration
|
||||
<Button fill="text" size="sm" onClick={handleGoToTemplateSettingsCllick}>
|
||||
|
|
@ -240,7 +230,7 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
|
|||
<Text type="secondary">
|
||||
Press <Text keyboard>Ctrl</Text>+<Text keyboard>Space</Text> to get suggestions
|
||||
</Text>
|
||||
{activeGroup === 'web' && activeTemplate.name == 'web_title_template' && (
|
||||
{activeGroup === 'web' && activeTemplate.name === 'web_title_template' && (
|
||||
<div className={cx('web-title-message')}>
|
||||
<Text type="secondary" size="small">
|
||||
Please note that after changing the web title template new alert groups will be searchable by
|
||||
|
|
@ -271,7 +261,7 @@ const AlertTemplatesForm = (props: AlertTemplatesFormProps) => {
|
|||
<VerticalGroup>
|
||||
<Label>{`${capitalCase(activeGroup)} Preview`}</Label>
|
||||
<VerticalGroup style={{ width: '100%' }}>
|
||||
{groups[activeGroup].map((template: any) => (
|
||||
{groups[activeGroup].map((template) => (
|
||||
<TemplatePreview
|
||||
active={template.name === activeTemplate?.name}
|
||||
key={template.name}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { describe, expect, test } from '@jest/globals';
|
||||
import { render, fireEvent, screen } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import Avatar from 'components/Avatar/Avatar';
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
.root_selected::before {
|
||||
display: block;
|
||||
content: "";
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import React from 'react';
|
|||
import { describe, expect, test } from '@jest/globals';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import CardButton from 'components/CardButton/CardButton';
|
||||
|
||||
|
|
@ -23,11 +22,11 @@ describe('CardButton', () => {
|
|||
const onClickMock = jest.fn();
|
||||
render(<CardButton {...getProps(onClickMock)} />);
|
||||
|
||||
const rootEl = getRootBlockEl()
|
||||
const rootEl = getRootBlockEl();
|
||||
|
||||
fireEvent.click(rootEl);
|
||||
|
||||
expect(rootEl.classList).toContain("root_selected")
|
||||
expect(rootEl.classList).toContain('root_selected');
|
||||
expect(onClickMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,12 @@ const CardButton: FC<CardButtonProps> = (props) => {
|
|||
}, [selected]);
|
||||
|
||||
return (
|
||||
<Block onClick={handleClick} withBackground className={cx('root', { root_selected: selected })} data-testid='test__cardButton'>
|
||||
<Block
|
||||
onClick={handleClick}
|
||||
withBackground
|
||||
className={cx('root', { root_selected: selected })}
|
||||
data-testid="test__cardButton"
|
||||
>
|
||||
<div className={cx('icon')}>{icon}</div>
|
||||
<div className={cx('meta')}>
|
||||
<VerticalGroup spacing="xs">
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ describe('Collapse', () => {
|
|||
return {
|
||||
label: 'Toggle',
|
||||
isOpen: isOpen,
|
||||
onClick: onClick
|
||||
} as CollapseProps
|
||||
onClick: onClick,
|
||||
} as CollapseProps;
|
||||
}
|
||||
|
||||
test('Content becomes visible on click', () => {
|
||||
|
|
@ -34,7 +34,7 @@ describe('Collapse', () => {
|
|||
|
||||
const content = getChildrenEl();
|
||||
expect(content).toBeNull();
|
||||
})
|
||||
});
|
||||
|
||||
test('Content is not collapsed for [isOpen=true]', () => {
|
||||
render(<Collapse {...getProps(true)} />);
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.root {
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -2,12 +2,9 @@ import React, { FC, useCallback, useEffect, useState } from 'react';
|
|||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Button, HorizontalGroup, Icon, Select } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import Text from 'components/Text/Text';
|
||||
|
||||
import styles from './CursorPagination.module.css';
|
||||
|
||||
interface CursorPaginationProps {
|
||||
current: string;
|
||||
onChange: (cursor: string, direction: 'prev' | 'next') => void;
|
||||
|
|
@ -18,8 +15,6 @@ interface CursorPaginationProps {
|
|||
next: string;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const CursorPagination: FC<CursorPaginationProps> = (props) => {
|
||||
const { current, onChange, prev, next, itemsPerPage, itemsPerPageOptions, onChangeItemsPerPage } = props;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { ChangeEvent, FC, useCallback } from 'react';
|
||||
|
||||
import { Icon, Input, Button, IconButton } from '@grafana/ui';
|
||||
import { Icon, Input, IconButton } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import styles from './EscalationsFilters.module.css';
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.root {
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -2,18 +2,14 @@ import React, { useCallback } from 'react';
|
|||
|
||||
import { Field, Form, Input, InputControl, Select, Switch, TextArea } from '@grafana/ui';
|
||||
import { capitalCase } from 'change-case';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import { FormItem, FormItemType } from 'components/GForm/GForm.types';
|
||||
import GSelect from 'containers/GSelect/GSelect';
|
||||
import RemoteSelect from 'containers/RemoteSelect/RemoteSelect';
|
||||
|
||||
import styles from './GForm.module.css';
|
||||
|
||||
interface GFormProps {
|
||||
form: { name: string; fields: FormItem[] };
|
||||
data: any;
|
||||
/* errors: { [key: string]: string }; */
|
||||
onSubmit: (data: any) => void;
|
||||
}
|
||||
|
||||
|
|
@ -21,8 +17,6 @@ const nullNormalizer = (value: string) => {
|
|||
return value || null;
|
||||
};
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
function renderFormControl(formItem: FormItem, register: any, control: any) {
|
||||
switch (formItem.type) {
|
||||
case FormItemType.Input:
|
||||
|
|
@ -99,20 +93,18 @@ const GForm = (props: GFormProps) => {
|
|||
return (
|
||||
<Form maxWidth="none" id={form.name} defaultValues={data} onSubmit={handleSubmit}>
|
||||
{({ register, errors, control }) => {
|
||||
return form.fields.map((formItem: FormItem, formIndex: number) => {
|
||||
return (
|
||||
<Field
|
||||
key={formIndex}
|
||||
disabled={formItem.getDisabled ? formItem.getDisabled(data) : false}
|
||||
label={formItem.label || capitalCase(formItem.name)}
|
||||
invalid={!!errors[formItem.name]}
|
||||
error={`${capitalCase(formItem.name)} is required`}
|
||||
description={formItem.description}
|
||||
>
|
||||
{renderFormControl(formItem, register, control)}
|
||||
</Field>
|
||||
);
|
||||
});
|
||||
return form.fields.map((formItem: FormItem, formIndex: number) => (
|
||||
<Field
|
||||
key={formIndex}
|
||||
disabled={formItem.getDisabled ? formItem.getDisabled(data) : false}
|
||||
label={formItem.label || capitalCase(formItem.name)}
|
||||
invalid={!!errors[formItem.name]}
|
||||
error={`${capitalCase(formItem.name)} is required`}
|
||||
description={formItem.description}
|
||||
>
|
||||
{renderFormControl(formItem, register, control)}
|
||||
</Field>
|
||||
));
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { Moment } from 'moment';
|
||||
|
||||
export enum FormItemType {
|
||||
'Input' = 'input',
|
||||
'TextArea' = 'textarea',
|
||||
|
|
@ -7,13 +5,6 @@ export enum FormItemType {
|
|||
'GSelect' = 'gselect',
|
||||
'Switch' = 'switch',
|
||||
'RemoteSelect' = 'remoteselect',
|
||||
|
||||
/* 'InputNumber' = 'input-number',
|
||||
'Select' = 'select',
|
||||
'Switch' = 'switch',
|
||||
'ASelect' = 'aselect',
|
||||
'JustSelect' = 'just-select',
|
||||
'DatePicker' = 'datepicker', */
|
||||
}
|
||||
|
||||
export interface FormItem {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC, useState, useCallback, useRef, useEffect } from 'react';
|
||||
import React, { useCallback, useRef, useEffect } from 'react';
|
||||
|
||||
import { LoadingPlaceholder } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
|
@ -37,7 +37,6 @@ const GList = <T extends WithId>(props: GListProps<T>) => {
|
|||
const divToScroll = selectedElement.parentElement.parentElement;
|
||||
|
||||
const maxScroll = Math.max(0, selectedElement.parentElement.offsetHeight - divToScroll.offsetHeight);
|
||||
const minScroll = 0;
|
||||
|
||||
const scrollTop =
|
||||
selectedElement.offsetTop -
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC, useState, useCallback, useMemo, ChangeEvent } from 'react';
|
||||
import React, { FC, useCallback, useMemo, ChangeEvent } from 'react';
|
||||
|
||||
import { Pagination, Checkbox, Icon } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
|
@ -96,7 +96,7 @@ const GTable: FC<Props> = (props) => {
|
|||
|
||||
const handleMasterCheckboxChange = useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { selectedRowKeys, onChange } = rowSelection;
|
||||
const { onChange } = rowSelection;
|
||||
if (event.target.checked) {
|
||||
const newRowSelection = data.map((item: any) => item[rowKey as string]);
|
||||
onChange(newRowSelection);
|
||||
|
|
|
|||
|
|
@ -39,8 +39,7 @@ const Modal: FC<PropsWithChildren<ModalProps>> = (props) => {
|
|||
contentLabel={title}
|
||||
className={cx('root')}
|
||||
overlayClassName={cx('overlay')}
|
||||
overlayElement={(props, contentElement) => contentElement} // render without overlay to allow body scroll
|
||||
/* bodyOpenClassName={cx('body-open')} */
|
||||
overlayElement={(_props, contentElement) => contentElement} // render without overlay to allow body scroll
|
||||
contentElement={contentElement}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.root {
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -1,14 +1,11 @@
|
|||
import React, { FC, useCallback, useMemo, useRef, useEffect } from 'react';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
|
||||
import { CodeEditor, CodeEditorSuggestionItemKind, LoadingPlaceholder } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import { getPaths } from 'utils';
|
||||
|
||||
import { conf, language } from './jinja2';
|
||||
|
||||
import styles from './MonacoJinja2Editor.module.css';
|
||||
|
||||
declare const monaco: any;
|
||||
|
||||
interface MonacoJinja2EditorProps {
|
||||
|
|
@ -19,8 +16,6 @@ interface MonacoJinja2EditorProps {
|
|||
loading: boolean;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const PREDEFINED_TERMS = [
|
||||
'grafana_oncall_link',
|
||||
'integration_name',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { FC, useCallback, useState } from 'react';
|
||||
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
import { Button, Drawer, HorizontalGroup, Icon, VerticalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
|
|
@ -39,10 +38,6 @@ const NewScheduleSelector: FC<NewScheduleSelectorProps> = (props) => {
|
|||
<Drawer scrollableContent title="Create new schedule" onClose={onHide} closeOnMaskClick>
|
||||
<div className={cx('content')}>
|
||||
<VerticalGroup spacing="lg">
|
||||
{/*<Text type="secondary">
|
||||
Manage on-call schedules using your favourite calendar app, such as Google Calendar or Microsoft Outlook. To
|
||||
schedule on-call shifts create a new calendar and use events with the teammates usernames
|
||||
</Text>*/}
|
||||
<Block bordered withBackground className={cx('block')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<HorizontalGroup spacing="md">
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
|
|||
|
||||
import { Button, VerticalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { PropTypes } from 'mobx-react';
|
||||
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import Text from 'components/Text/Text';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { ChangeEvent } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Button, Input, Select, Tooltip, IconButton } from '@grafana/ui';
|
||||
import { Button, Input, Select, IconButton } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import moment from 'moment-timezone';
|
||||
import { SortableElement } from 'react-sortable-hoc';
|
||||
|
|
@ -11,7 +11,6 @@ import PluginLink from 'components/PluginLink/PluginLink';
|
|||
import TimeRange from 'components/TimeRange/TimeRange';
|
||||
import Timeline from 'components/Timeline/Timeline';
|
||||
import GSelect from 'containers/GSelect/GSelect';
|
||||
import RemoteSelect from 'containers/RemoteSelect/RemoteSelect';
|
||||
import UserTooltip from 'containers/UserTooltip/UserTooltip';
|
||||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
import { prepareEscalationPolicy } from 'models/escalation_policy/escalation_policy.helpers';
|
||||
|
|
@ -147,7 +146,7 @@ export class EscalationPolicy extends React.Component<EscalationPolicyProps, any
|
|||
className={cx('select', 'control', 'multiSelect')}
|
||||
value={notify_to_users_queue}
|
||||
onChange={this._getOnChangeHandler('notify_to_users_queue')}
|
||||
getOptionLabel={({ label, value }: SelectableValue) => <UserTooltip id={value} />}
|
||||
getOptionLabel={({ value }: SelectableValue) => <UserTooltip id={value} />}
|
||||
/>
|
||||
</WithPermissionControl>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { FadeTransition } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
|
||||
import styles from './ScheduleBorderedAvatar.module.scss';
|
||||
|
||||
import animationStyles from 'containers/Rotations/Rotations.module.css';
|
||||
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface ScheduleBorderedAvatarProps {
|
||||
|
|
@ -52,7 +46,9 @@ export default function ScheduleBorderedAvatar({
|
|||
}
|
||||
|
||||
function renderColorPaths(colors: string[]) {
|
||||
if (!colors?.length) {return null;}
|
||||
if (!colors?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const colorSchemeList = colors;
|
||||
if (colors.length === 1) {
|
||||
|
|
@ -75,7 +71,7 @@ export default function ScheduleBorderedAvatar({
|
|||
lastX = x;
|
||||
lastY = y;
|
||||
|
||||
return <path d={d} stroke={colors[colorIndex]} />;
|
||||
return <path key={colorIndex} d={d} stroke={colors[colorIndex]} />;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,15 +28,3 @@
|
|||
.tooltip {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
.tooltip__type_link {
|
||||
border: 1px solid #6CCF8E;
|
||||
background: #132322;
|
||||
}
|
||||
|
||||
.tooltip__type_warning {
|
||||
border: 1px solid #F8D06B;
|
||||
background: #3A301E;
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { HorizontalGroup, VerticalGroup, Icon, IconButton, Tooltip, IconName } from '@grafana/ui';
|
||||
import { HorizontalGroup, VerticalGroup, Icon, Tooltip, IconName } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import Text, { TextType } from 'components/Text/Text';
|
||||
|
|
@ -25,16 +25,6 @@ const typeToColor = {
|
|||
warning: 'warning',
|
||||
};
|
||||
|
||||
const typeToBorderColor = {
|
||||
link: '#6CCF8E',
|
||||
warning: '#F8D06B',
|
||||
};
|
||||
|
||||
const typeToBackgroundColor = {
|
||||
link: '#132322',
|
||||
warning: '#3A301E',
|
||||
};
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const ScheduleCounter: FC<ScheduleCounterProps> = (props) => {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
.hr {
|
||||
width: 100%;
|
||||
margin: 0 -11px;
|
||||
margin: 0 -11px;
|
||||
}
|
||||
|
||||
.times {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Icon, Button, HorizontalGroup, VerticalGroup } from '@grafana/ui';
|
||||
import { HorizontalGroup, VerticalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
|
|
@ -9,8 +9,6 @@ import Text from 'components/Text/Text';
|
|||
import { getTzOffsetString } from 'models/timezone/timezone.helpers';
|
||||
import { User } from 'models/user/user.types';
|
||||
|
||||
import Line from './img/line.svg';
|
||||
|
||||
import styles from './ScheduleUserDetails.module.css';
|
||||
|
||||
interface ScheduleUserDetailsProps {
|
||||
|
|
@ -20,30 +18,9 @@ interface ScheduleUserDetailsProps {
|
|||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
enum UserOncallStatus {
|
||||
Now = 'now',
|
||||
Outside = 'outside',
|
||||
Inside = 'inside',
|
||||
}
|
||||
|
||||
const userOncallStatusToText = {
|
||||
[UserOncallStatus.Now]: 'Oncall now',
|
||||
[UserOncallStatus.Inside]: 'Inside working hours',
|
||||
[UserOncallStatus.Outside]: 'Outside working hours',
|
||||
};
|
||||
|
||||
const ScheduleUserDetails: FC<ScheduleUserDetailsProps> = (props) => {
|
||||
const { user, currentMoment } = props;
|
||||
|
||||
const userStatus =
|
||||
Math.random() > 0.66
|
||||
? UserOncallStatus.Now
|
||||
: Math.random() > 0.33
|
||||
? UserOncallStatus.Inside
|
||||
: UserOncallStatus.Outside;
|
||||
|
||||
const userMoment = currentMoment.tz(user.timezone);
|
||||
|
||||
const userOffsetHoursStr = getTzOffsetString(userMoment);
|
||||
|
||||
return (
|
||||
|
|
@ -51,67 +28,12 @@ const ScheduleUserDetails: FC<ScheduleUserDetailsProps> = (props) => {
|
|||
<VerticalGroup spacing="sm">
|
||||
<HorizontalGroup justify="space-between">
|
||||
<Avatar src={user.avatar} size="large" />
|
||||
{/*<Button variant="secondary">
|
||||
<HorizontalGroup spacing="sm">
|
||||
<Icon name="bell" />
|
||||
Push
|
||||
</HorizontalGroup>
|
||||
</Button>*/}
|
||||
</HorizontalGroup>
|
||||
<VerticalGroup spacing="sm">
|
||||
<Text type="primary">{user.username}</Text>
|
||||
<Text type="secondary">
|
||||
{`${userMoment.tz(user.timezone).format('DD MMM, HH:mm')}`} {userOffsetHoursStr}
|
||||
</Text>
|
||||
{/* <div
|
||||
className={cx('oncall-badge', {
|
||||
[`oncall-badge__type_${userStatus}`]: true,
|
||||
})}
|
||||
>
|
||||
{userOncallStatusToText[userStatus]}
|
||||
</div>
|
||||
<HorizontalGroup>
|
||||
<VerticalGroup spacing="sm">
|
||||
<Text type="primary">Next shift</Text>
|
||||
<div className={cx('times')}>
|
||||
<HorizontalGroup>
|
||||
<img src={Line} />
|
||||
<VerticalGroup spacing="none">
|
||||
<Text type="secondary">30 apr, 00:00</Text>
|
||||
<Text type="secondary">30 apr, 23:59</Text>
|
||||
</VerticalGroup>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</VerticalGroup>
|
||||
<VerticalGroup spacing="sm">
|
||||
<Text type="primary">Last shift</Text>
|
||||
<div className={cx('times')}>
|
||||
<HorizontalGroup>
|
||||
<img src={Line} />
|
||||
<VerticalGroup spacing="none">
|
||||
<Text type="secondary">30 apr, 00:00</Text>
|
||||
<Text type="secondary">30 apr, 23:59</Text>
|
||||
</VerticalGroup>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</VerticalGroup>
|
||||
</HorizontalGroup>
|
||||
</VerticalGroup>
|
||||
<hr style={{ width: '100%' }} />
|
||||
<VerticalGroup spacing="sm">
|
||||
<Text type="primary">Contacts</Text>
|
||||
<HorizontalGroup spacing="sm">
|
||||
<Icon className={cx('icon')} name="message" />
|
||||
<Text type="link">mail@grafana.com</Text>
|
||||
</HorizontalGroup>
|
||||
<HorizontalGroup spacing="sm">
|
||||
<Icon className={cx('icon')} name="slack" />
|
||||
<Text type="link">@slackid</Text>
|
||||
</HorizontalGroup>
|
||||
<HorizontalGroup spacing="sm">
|
||||
<Icon className={cx('icon')} name="phone" />
|
||||
<Text type="secondary">+39 555 449 00 00</Text>
|
||||
</HorizontalGroup>*/}
|
||||
</VerticalGroup>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import moment from 'moment';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
export function optionToDateString(option: string) {
|
||||
switch (option) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { ChangeEvent, useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { DatePickerWithInput, Field, HorizontalGroup, RadioButtonGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import moment from 'moment';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import { dateStringToOption, optionToDateString } from './SchedulesFilters.helpers';
|
||||
import { SchedulesFiltersType } from './SchedulesFilters.types';
|
||||
|
|
@ -17,18 +17,22 @@ interface SchedulesFiltersProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
const SchedulesFilters = (props: SchedulesFiltersProps) => {
|
||||
const { value, onChange, className } = props;
|
||||
|
||||
const handleDateChange = useCallback((date: Date) => {
|
||||
onChange({ selectedDate: moment(date).format('YYYY-MM-DD') });
|
||||
}, []);
|
||||
const SchedulesFilters = ({ value, onChange, className }: SchedulesFiltersProps) => {
|
||||
const handleDateChange = useCallback(
|
||||
(date: Date) => {
|
||||
onChange({ selectedDate: moment(date).format('YYYY-MM-DD') });
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const option = useMemo(() => dateStringToOption(value.selectedDate), [value]);
|
||||
|
||||
const handleOptionChange = useCallback((option: string) => {
|
||||
onChange({ ...value, selectedDate: optionToDateString(option) });
|
||||
}, []);
|
||||
const handleOptionChange = useCallback(
|
||||
(option: string) => {
|
||||
onChange({ ...value, selectedDate: optionToDateString(option) });
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const datePickerValue = useMemo(() => moment(value.selectedDate).toDate(), [value]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { Moment } from 'moment';
|
||||
|
||||
export interface SchedulesFiltersType {
|
||||
selectedDate: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import moment from 'moment';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
export function optionToDateString(option: string) {
|
||||
switch (option) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import React, { ChangeEvent, useCallback, useMemo, useState } from 'react';
|
||||
import React, { ChangeEvent, useCallback } from 'react';
|
||||
|
||||
import { DatePickerWithInput, Field, HorizontalGroup, Icon, Input, RadioButtonGroup } from '@grafana/ui';
|
||||
import { Field, HorizontalGroup, Icon, Input, RadioButtonGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import { ScheduleType } from 'models/schedule/schedule.types';
|
||||
|
||||
import { dateStringToOption, optionToDateString } from './SchedulesFilters.helpers';
|
||||
import { SchedulesFiltersType } from './SchedulesFilters.types';
|
||||
|
||||
import styles from './SchedulesFilters.module.css';
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@
|
|||
}
|
||||
.copyButton {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import 'jest/matchMedia.ts';
|
|||
import React from 'react';
|
||||
|
||||
import { describe, expect, test } from '@jest/globals';
|
||||
import { render, fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import SourceCode from './SourceCode';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Button, Icon, IconButton } from '@grafana/ui';
|
||||
import { Button, IconButton } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
|
||||
|
|
@ -33,7 +33,13 @@ const SourceCode: FC<SourceCodeProps> = (props) => {
|
|||
{showClipboardIconOnly ? (
|
||||
<IconButton className={cx('copyIcon')} size={'lg'} name="copy" data-testid="test__copyIcon" />
|
||||
) : (
|
||||
<Button className={cx('copyButton')} variant="primary" size="xs" icon="copy" data-testid="test__copyIconWithText">
|
||||
<Button
|
||||
className={cx('copyButton')}
|
||||
variant="primary"
|
||||
size="xs"
|
||||
icon="copy"
|
||||
data-testid="test__copyIconWithText"
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { FC, useState, useCallback, useMemo, ChangeEvent } from 'react';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { Pagination, Checkbox, Icon, VerticalGroup } from '@grafana/ui';
|
||||
import { Pagination, VerticalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import Table from 'rc-table';
|
||||
import { TableProps } from 'rc-table/lib/Table';
|
||||
|
|
@ -34,21 +34,20 @@ export interface Props<RecordType = unknown> extends TableProps<RecordType> {
|
|||
|
||||
const GTable: FC<Props> = (props) => {
|
||||
const { columns, data, className, pagination, loading, rowKey, expandable, ...restProps } = props;
|
||||
|
||||
const { page, total: numberOfPages, onChange: onNavigate } = pagination || {};
|
||||
|
||||
const expandableFn = useMemo(() => {
|
||||
return expandable
|
||||
? {
|
||||
...expandable,
|
||||
expandIcon: ({ expanded, record }) => {
|
||||
expandIcon: ({ expanded }) => {
|
||||
return (
|
||||
<div className={cx('expand-icon', { [`expand-icon__expanded`]: expanded })}>
|
||||
<ExpandIcon />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
expandedRowClassName: (record, index) => (index % 2 === 0 ? cx('row-even') : cx('row-odd')),
|
||||
expandedRowClassName: (_record, index) => (index % 2 === 0 ? cx('row-even') : cx('row-odd')),
|
||||
}
|
||||
: null;
|
||||
}, [expandable]);
|
||||
|
|
@ -61,7 +60,7 @@ const GTable: FC<Props> = (props) => {
|
|||
columns={columns}
|
||||
data={data}
|
||||
expandable={expandableFn}
|
||||
rowClassName={(record, index) => (index % 2 === 0 ? cx('row-even') : cx('row-odd'))}
|
||||
rowClassName={(_record, index) => (index % 2 === 0 ? cx('row-even') : cx('row-odd'))}
|
||||
{...restProps}
|
||||
/>
|
||||
{pagination && (
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.keyboard {
|
||||
margin: 0 0.2em;
|
||||
padding: 0.15em 0.4em 0.1em;
|
||||
|
|
|
|||
|
|
@ -69,9 +69,8 @@ const Text: TextInterface = (props) => {
|
|||
|
||||
const handleConfirmEdit = useCallback(() => {
|
||||
setIsEditMode(false);
|
||||
|
||||
onTextChange(value);
|
||||
}, [value]);
|
||||
}, [value, onTextChange]);
|
||||
|
||||
const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
|
|
@ -15,21 +15,19 @@ export interface TimelineItemProps {
|
|||
children?: any;
|
||||
}
|
||||
|
||||
const TimelineItem: React.FC<TimelineItemProps> = (props) => {
|
||||
const { className, contentClassName, children, color = '#3274D9', number } = props;
|
||||
|
||||
const style = { backgroundColor: color };
|
||||
|
||||
return (
|
||||
<li className={cx('item', className)}>
|
||||
{/*<Badge count={badge} style={style} showZero={false}>*/}
|
||||
<div className={cx('dot')} style={{ backgroundColor: color }}>
|
||||
{number}
|
||||
</div>
|
||||
{/*</Badge>*/}
|
||||
<div className={cx('content', contentClassName)}>{children}</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
const TimelineItem: React.FC<TimelineItemProps> = ({
|
||||
className,
|
||||
contentClassName,
|
||||
children,
|
||||
color = '#3274D9',
|
||||
number,
|
||||
}) => (
|
||||
<li className={cx('item', className)}>
|
||||
<div className={cx('dot')} style={{ backgroundColor: color }}>
|
||||
{number}
|
||||
</div>
|
||||
<div className={cx('content', contentClassName)}>{children}</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
export default TimelineItem;
|
||||
|
|
|
|||
|
|
@ -49,8 +49,9 @@ const TimelineMarks: FC<TimelineMarksProps> = (props) => {
|
|||
<div className={cx('root')}>
|
||||
{debug && (
|
||||
<svg version="1.1" width="100%" height="6px" xmlns="http://www.w3.org/2000/svg" className={cx('debug-scale')}>
|
||||
{cuts.map((cut, index) => (
|
||||
{cuts.map((_cut, index) => (
|
||||
<line
|
||||
key={index}
|
||||
x1={`${(index * 100) / (24 * 7)}%`}
|
||||
strokeWidth={1}
|
||||
y1="0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Button, VerticalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import Block from 'components/GBlock/Block';
|
||||
|
|
@ -13,7 +12,6 @@ import scheduleIcon from './icons/calendar-icon.svg';
|
|||
import chatIcon from './icons/chat-icon.svg';
|
||||
import escalationIcon from './icons/escalation-icon.svg';
|
||||
import integrationsIcon from './icons/integration-icon.svg';
|
||||
import arrowIcon from './icons/long-arrow.svg';
|
||||
|
||||
import styles from './Tutorial.module.css';
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Item } from './UserGroups.types';
|
|||
|
||||
export const toPlainArray = (groups: string[][]) => {
|
||||
const items: Item[] = [];
|
||||
groups.forEach((group: string[], groupIndex: number) => {
|
||||
groups.forEach((_group: string[], groupIndex: number) => {
|
||||
items.push({
|
||||
key: `group-${groupIndex}`,
|
||||
type: 'group',
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
.separator::before {
|
||||
display: block;
|
||||
content: "";
|
||||
content: '';
|
||||
flex-grow: 1;
|
||||
border-bottom: var(--border-medium);
|
||||
height: 0;
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
.separator::after {
|
||||
display: block;
|
||||
content: "";
|
||||
content: '';
|
||||
flex-grow: 1;
|
||||
border-bottom: var(--border-medium);
|
||||
height: 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { VerticalGroup, HorizontalGroup, IconButton, Field, Input } from '@grafana/ui';
|
||||
import { VerticalGroup, HorizontalGroup, IconButton } from '@grafana/ui';
|
||||
import { arrayMoveImmutable } from 'array-move';
|
||||
import cn from 'classnames/bind';
|
||||
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
|
||||
|
|
@ -45,7 +45,7 @@ const UserGroups = (props: UserGroupsProps) => {
|
|||
k++;
|
||||
|
||||
if (k === index) {
|
||||
newGroups[i] = newGroups[i].filter((item, itemIndex) => itemIndex !== j);
|
||||
newGroups[i] = newGroups[i].filter((_item, itemIndex) => itemIndex !== j);
|
||||
onChange(newGroups.filter((group) => group.length));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import React, { FC, useCallback, useMemo } from 'react';
|
|||
import { Select } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import { get } from 'lodash-es';
|
||||
|
||||
import { getTzOffsetString } from 'models/timezone/timezone.helpers';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ const UsersFilters = (props: UsersFiltersProps) => {
|
|||
<HorizontalGroup>
|
||||
{roleOptions.map((option) => (
|
||||
<Checkbox
|
||||
key={option.value}
|
||||
value={value.roles.includes(option.value)}
|
||||
label={option.label}
|
||||
onChange={onChangeRolesCallback(option.value)}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,22 @@ import cn from 'classnames/bind';
|
|||
|
||||
import styles from './VerticalTabsBar.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
interface TabProps {
|
||||
id: string;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export const VerticalTab: FC<TabProps> = ({ children }) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
interface VerticalTabsBarProps {
|
||||
children: Array<React.ReactElement<TabProps>> | React.ReactElement<TabProps>;
|
||||
activeTab: string;
|
||||
onChange: (id: string) => void;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const VerticalTabsBar = (props: VerticalTabsBarProps) => {
|
||||
const { children, activeTab, onChange } = props;
|
||||
|
||||
|
|
@ -25,8 +33,9 @@ const VerticalTabsBar = (props: VerticalTabsBarProps) => {
|
|||
<div className={cx('root')}>
|
||||
{React.Children.toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((child: React.ReactElement) => (
|
||||
.map((child: React.ReactElement, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={getClickHandler(child.props.id)}
|
||||
className={cx('tab', { tab_active: activeTab === child.props.id })}
|
||||
>
|
||||
|
|
@ -38,12 +47,3 @@ const VerticalTabsBar = (props: VerticalTabsBarProps) => {
|
|||
};
|
||||
|
||||
export default VerticalTabsBar;
|
||||
|
||||
interface TabProps {
|
||||
id: string;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export const VerticalTab: FC<TabProps> = ({ children }) => {
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.root {
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
import React, { FC, ReactElement, useCallback, useState } from 'react';
|
||||
import React, { ReactElement, useCallback, useState } from 'react';
|
||||
|
||||
import { ConfirmModal } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import styles from './WithConfirm.module.css';
|
||||
|
||||
interface WithConfirmProps {
|
||||
children: ReactElement;
|
||||
|
|
@ -13,8 +10,6 @@ interface WithConfirmProps {
|
|||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const WithConfirm = (props: WithConfirmProps) => {
|
||||
const { children, title = 'Are you sure to delete?', body, confirmText = 'Delete', disabled } = props;
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const getNonWorkingMoments = (startMoment, endMoment, workingHours) => {
|
|||
const nonWorkingMoments = [{ start: startMoment, end: endMoment }];
|
||||
|
||||
let lastNonWorkingRange = nonWorkingMoments[0];
|
||||
for (const [i, range] of workingHours.entries()) {
|
||||
for (const [_i, range] of workingHours.entries()) {
|
||||
lastNonWorkingRange.end = range.start;
|
||||
|
||||
lastNonWorkingRange = { start: range.end, end: undefined };
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React, { FC, useMemo } from 'react';
|
|||
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import localeData from 'dayjs/plugin/localeData';
|
||||
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
|
||||
|
|
@ -11,8 +10,6 @@ import { getNonWorkingMoments, getWorkingMoments } from './WorkingHours.helpers'
|
|||
|
||||
import styles from './WorkingHours.module.css';
|
||||
|
||||
import { start } from 'repl';
|
||||
|
||||
interface WorkingHoursProps {
|
||||
timezone: Timezone;
|
||||
workingHours: any;
|
||||
|
|
@ -34,33 +31,11 @@ const WorkingHours: FC<WorkingHoursProps> = (props) => {
|
|||
[startMoment, endMoment, workingHours, timezone]
|
||||
);
|
||||
|
||||
/*console.log(
|
||||
workingMoments.map(({ start, end }) => `${start.diff(startMoment, 'hours')} - ${end.diff(startMoment, 'hours')}`)
|
||||
);*/
|
||||
|
||||
const nonWorkingMoments = useMemo(
|
||||
() => getNonWorkingMoments(startMoment, endMoment, workingMoments),
|
||||
[startMoment, endMoment, workingMoments]
|
||||
);
|
||||
|
||||
// console.log(startMoment, startMoment.toString());
|
||||
|
||||
/* console.log(
|
||||
workingMoments.map(
|
||||
(range) =>
|
||||
`${range.start.tz(timezone).format('D MMM ddd HH:ss')} - ${range.end.tz(timezone).format('D MMM ddd HH:ss')}`
|
||||
)
|
||||
); */
|
||||
|
||||
// console.log(workingHours);
|
||||
|
||||
/*console.log(
|
||||
nonWorkingMoments.map(
|
||||
(range) =>
|
||||
`${range.start.tz(timezone).format('D MMM ddd HH:ss')} - ${range.end.tz(timezone).format('D MMM ddd HH:ss')}`
|
||||
)
|
||||
);*/
|
||||
|
||||
return (
|
||||
<svg
|
||||
version="1.1"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Icon, Tooltip, HorizontalGroup, VerticalGroup } from '@grafana/ui';
|
||||
import { Tooltip, HorizontalGroup, VerticalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import Emoji from 'react-emoji-render';
|
||||
|
|
@ -8,9 +8,8 @@ import Emoji from 'react-emoji-render';
|
|||
import IntegrationLogo from 'components/IntegrationLogo/IntegrationLogo';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import Text from 'components/Text/Text';
|
||||
import { GrafanaIcon, HeartGreenIcon, HeartRedIcon } from 'icons';
|
||||
import { HeartGreenIcon, HeartRedIcon } from 'icons';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { SelectOption } from 'state/types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from './AlertReceiveChannelCard.module.css';
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import { ChannelFilter } from 'models/channel_filter/channel_filter.types';
|
|||
import { EscalationChain } from 'models/escalation_chain/escalation_chain.types';
|
||||
import { EscalationPolicyOption } from 'models/escalation_policy/escalation_policy.types';
|
||||
import { MaintenanceType } from 'models/maintenance/maintenance.types';
|
||||
import { getSlackChannelName } from 'models/slack_channel/slack_channel.helpers';
|
||||
import { WithStoreProps } from 'state/types';
|
||||
import { UserAction } from 'state/userAction';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
|
@ -63,6 +62,14 @@ interface AlertRulesState {
|
|||
editIntegrationName?: string;
|
||||
}
|
||||
|
||||
const Notification: React.FC = () => (
|
||||
<div>
|
||||
Demo alert was generated. Find it on the
|
||||
<PluginLink query={{ page: 'incidents' }}> "Alert Groups" </PluginLink>
|
||||
page and make sure it didn't freak out your colleagues 😉
|
||||
</div>
|
||||
);
|
||||
|
||||
@observer
|
||||
class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
||||
state: AlertRulesState = {
|
||||
|
|
@ -77,9 +84,7 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<AlertRulesProps>, prevState: Readonly<AlertRulesState>, snapshot?: any) {
|
||||
const { store } = this.props;
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<AlertRulesProps>, _prevState: Readonly<AlertRulesState>, _snapshot?: any) {
|
||||
if (this.props.alertReceiveChannelId && prevProps.alertReceiveChannelId !== this.props.alertReceiveChannelId) {
|
||||
if (prevProps.alertReceiveChannelId) {
|
||||
this.setState({
|
||||
|
|
@ -130,14 +135,12 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
render() {
|
||||
const {
|
||||
alertReceiveChannelIdToCreateChannelFilter,
|
||||
channelFilterToEdit,
|
||||
settingsVisible,
|
||||
routeToDelete,
|
||||
escalationChainIdToCopy,
|
||||
channelFilterIdToCopyEscalationChain,
|
||||
editIntegrationName,
|
||||
} = this.state;
|
||||
const { store, alertReceiveChannelId, onEditAlertReceiveChannelTemplates, onShowSettings } = this.props;
|
||||
const { store, alertReceiveChannelId, onShowSettings } = this.props;
|
||||
const { alertReceiveChannelStore } = store;
|
||||
|
||||
const alertReceiveChannel = alertReceiveChannelStore.items[alertReceiveChannelId];
|
||||
|
|
@ -153,7 +156,6 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
<div className={cx('root')}>
|
||||
<Block className={cx('headerBlock')}>
|
||||
<div className={cx('header')}>
|
||||
{/* <HorizontalGroup> */}
|
||||
<Text.Title level={4}>
|
||||
<HorizontalGroup>
|
||||
Escalate
|
||||
|
|
@ -197,7 +199,6 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
{/* </HorizontalGroup> */}
|
||||
<div className={cx('buttons')}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
|
@ -377,7 +378,7 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
handleEscalationChainCreate = async (id: EscalationChain['id']) => {
|
||||
const { store } = this.props;
|
||||
const { alertReceiveChannelStore } = store;
|
||||
const { escalationChainIdToCopy, channelFilterIdToCopyEscalationChain } = this.state;
|
||||
const { channelFilterIdToCopyEscalationChain } = this.state;
|
||||
|
||||
await alertReceiveChannelStore
|
||||
.saveChannelFilter(channelFilterIdToCopyEscalationChain, { escalation_chain: id })
|
||||
|
|
@ -389,8 +390,7 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
};
|
||||
|
||||
handleDeleteAlertReceiveChannel = () => {
|
||||
const { store, alertReceiveChannelId, onDelete } = this.props;
|
||||
|
||||
const { alertReceiveChannelId, onDelete } = this.props;
|
||||
onDelete(alertReceiveChannelId);
|
||||
};
|
||||
|
||||
|
|
@ -460,12 +460,13 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
|
||||
return (
|
||||
<div>
|
||||
{channelFilterIds.map((channelFilterId: ChannelFilter['id'], index: number) => {
|
||||
{channelFilterIds.map((channelFilterId: ChannelFilter['id']) => {
|
||||
const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
|
||||
|
||||
if (channelFilterId === channelFilterToEdit?.id) {
|
||||
return (
|
||||
<ChannelFilterForm
|
||||
key={channelFilterId}
|
||||
className={cx('route')}
|
||||
id={channelFilterToEdit.id}
|
||||
alertReceiveChannelId={channelFilterToEdit.alert_receive_channel}
|
||||
|
|
@ -590,8 +591,6 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
};
|
||||
|
||||
getChannelFilterToggleHandler = (channelFilterId: ChannelFilter['id']) => {
|
||||
const { store } = this.props;
|
||||
|
||||
return (isOpen: boolean) => {
|
||||
const { expandedRoutes } = this.state;
|
||||
|
||||
|
|
@ -685,17 +684,9 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
channelFilterId: ChannelFilter['id']
|
||||
) => {
|
||||
const { store } = this.props;
|
||||
|
||||
const { telegramChannelStore, teamStore } = store;
|
||||
const channelFilterIds = store.alertReceiveChannelStore.channelFilterIds[alertReceiveChannelId];
|
||||
|
||||
const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
|
||||
|
||||
const telegramChannel =
|
||||
channelFilter.telegram_channel && telegramChannelStore.items[channelFilter.telegram_channel];
|
||||
|
||||
const slackChannelName = getSlackChannelName(channelFilter.slack_channel || teamStore.currentTeam?.slack_channel);
|
||||
|
||||
const index = channelFilterIds.indexOf(channelFilterId);
|
||||
return (
|
||||
<div className={cx('channel-filter-header')}>
|
||||
|
|
@ -760,9 +751,7 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
|
||||
_renderEscalationPolicies = (channelFilterId: ChannelFilter['id']) => {
|
||||
const { store } = this.props;
|
||||
|
||||
const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
|
||||
|
||||
const escalationChainId = channelFilter.escalation_chain;
|
||||
|
||||
return (
|
||||
|
|
@ -802,13 +791,7 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
return () => {
|
||||
alertReceiveChannelStore.sendDemoAlert(id).then(() => {
|
||||
alertReceiveChannelStore.updateCounters();
|
||||
openNotification(
|
||||
<div>
|
||||
Demo alert was generated. Find it on the
|
||||
<PluginLink query={{ page: 'incidents' }}> "Alert Groups" </PluginLink>
|
||||
page and make sure it didn't freak out your colleagues 😉
|
||||
</div>
|
||||
);
|
||||
openNotification(<Notification />);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
@ -819,13 +802,7 @@ class AlertRules extends React.Component<AlertRulesProps, AlertRulesState> {
|
|||
} = this.props;
|
||||
return () => {
|
||||
alertReceiveChannelStore.sendDemoAlertToParticularRoute(id).then(() => {
|
||||
openNotification(
|
||||
<div>
|
||||
Demo alert was generated. Find it on the
|
||||
<PluginLink query={{ page: 'incidents' }}> "Alert Groups" </PluginLink>
|
||||
page and make sure it didn't freak out your colleagues 😉
|
||||
</div>
|
||||
);
|
||||
openNotification(<Notification />);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const SlackConnector = (props: SlackConnectorProps) => {
|
|||
|
||||
const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
|
||||
|
||||
const handleSlackChannelChange = useCallback((value: SlackChannel['id'], slackChannel: SlackChannel) => {
|
||||
const handleSlackChannelChange = useCallback((_value: SlackChannel['id'], slackChannel: SlackChannel) => {
|
||||
// @ts-ignore actually slack_channel is just slack_channel_id when saving
|
||||
alertReceiveChannelStore.saveChannelFilter(channelFilterId, { slack_channel: slackChannel?.slack_id || null });
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -18,15 +18,11 @@ interface TelegramConnectorProps {
|
|||
channelFilterId: ChannelFilter['id'];
|
||||
}
|
||||
|
||||
const TelegramConnector = (props: TelegramConnectorProps) => {
|
||||
const { channelFilterId } = props;
|
||||
const TelegramConnector = ({ channelFilterId }: TelegramConnectorProps) => {
|
||||
const { alertReceiveChannelStore } = useStore();
|
||||
const channelFilter = alertReceiveChannelStore.channelFilters[channelFilterId];
|
||||
|
||||
const store = useStore();
|
||||
const { teamStore, alertReceiveChannelStore } = store;
|
||||
|
||||
const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
|
||||
|
||||
const handleTelegramChannelChange = useCallback((value: TelegramChannel['id'], telegramChannel: TelegramChannel) => {
|
||||
const handleTelegramChannelChange = useCallback((_value: TelegramChannel['id'], telegramChannel: TelegramChannel) => {
|
||||
alertReceiveChannelStore.saveChannelFilter(channelFilterId, { telegram_channel: telegramChannel?.id || null });
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.root {
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { VerticalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import Timeline from 'components/Timeline/Timeline';
|
||||
import SlackConnector from 'containers/AlertRules/parts/connectors/SlackConnector';
|
||||
|
|
@ -9,10 +8,6 @@ import TelegramConnector from 'containers/AlertRules/parts/connectors/TelegramCo
|
|||
import { ChannelFilter } from 'models/channel_filter';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from 'containers/AlertRules/parts/index.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface ChatOpsConnectorsProps {
|
||||
channelFilterId: ChannelFilter['id'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Button } from '@grafana/ui';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import AlertTemplatesForm from 'components/AlertTemplates/AlertTemplatesForm';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel';
|
||||
import { Alert } from 'models/alertgroup/alertgroup.types';
|
||||
import { RootStore } from 'state';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { openNotification } from 'utils';
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import SourceCode from 'components/SourceCode/SourceCode';
|
|||
import { ApiToken } from 'models/api_token/api_token.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { openErrorNotification, openNotification } from 'utils';
|
||||
import { getItem } from 'utils/localStorage';
|
||||
|
||||
import styles from './ApiTokenForm.module.css';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import { Button, HorizontalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import moment from 'moment';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import GTable from 'components/GTable/GTable';
|
||||
import Text from 'components/Text/Text';
|
||||
|
|
@ -74,13 +74,6 @@ class ApiTokens extends React.Component<ApiTokensProps, any> {
|
|||
<div className={cx('header')}>
|
||||
<HorizontalGroup align="flex-end">
|
||||
<Text.Title level={3}>API Tokens</Text.Title>
|
||||
{/*<a target="_blank" href="https://a-03-dev-us-central-0.grafana.net/api-docs/#introduction">
|
||||
API Docs
|
||||
</a>
|
||||
<Text type="secondary">|</Text>
|
||||
<a target="_blank" href="https://github.com/grafana/amixr/tree/dev/docs/terraform-provider">
|
||||
Terraform Docs
|
||||
</a>*/}
|
||||
</HorizontalGroup>
|
||||
<WithPermissionControl userAction={UserAction.UpdateApiTokens}>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -4,13 +4,11 @@ import { SelectableValue } from '@grafana/data';
|
|||
import { Button, Field, HorizontalGroup, Icon, Modal } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import moment from 'moment';
|
||||
import Emoji from 'react-emoji-render';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import Text from 'components/Text/Text';
|
||||
import GSelect from 'containers/GSelect/GSelect';
|
||||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
import { AlertGroupStore } from 'models/alertgroup/alertgroup';
|
||||
import { Alert } from 'models/alertgroup/alertgroup.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { UserAction } from 'state/userAction';
|
||||
|
|
@ -25,9 +23,11 @@ interface AttachIncidentFormProps {
|
|||
onHide: () => void;
|
||||
}
|
||||
|
||||
const AttachIncidentForm = observer((props: AttachIncidentFormProps) => {
|
||||
const { id, onUpdate, onHide } = props;
|
||||
interface GroupedAlertNumberProps {
|
||||
value: Alert['pk'];
|
||||
}
|
||||
|
||||
const AttachIncidentForm = observer(({ id, onUpdate, onHide }: AttachIncidentFormProps) => {
|
||||
const store = useStore();
|
||||
|
||||
const { alertGroupStore } = store;
|
||||
|
|
@ -43,17 +43,10 @@ const AttachIncidentForm = observer((props: AttachIncidentFormProps) => {
|
|||
onHide();
|
||||
onUpdate();
|
||||
});
|
||||
}, [selected]);
|
||||
}, [selected, alertGroupStore, id, onHide, onUpdate]);
|
||||
|
||||
interface GroupedAlertNumberProps {
|
||||
value: Alert['pk'];
|
||||
}
|
||||
|
||||
const GroupedAlertNumber = observer((props: GroupedAlertNumberProps) => {
|
||||
const store = useStore();
|
||||
const { value } = props;
|
||||
|
||||
const { alertGroupStore } = store;
|
||||
const GroupedAlertNumber = observer(({ value }: GroupedAlertNumberProps) => {
|
||||
const { alertGroupStore } = useStore();
|
||||
const alert = alertGroupStore.items[value];
|
||||
|
||||
return (
|
||||
|
|
@ -88,7 +81,7 @@ const AttachIncidentForm = observer((props: AttachIncidentFormProps) => {
|
|||
displayField="render_for_web.title"
|
||||
placeholder="Select Incident"
|
||||
className={cx('select', 'control')}
|
||||
filterOptions={(id) => id !== props.id}
|
||||
filterOptions={(optionId) => optionId !== id}
|
||||
value={selected}
|
||||
onChange={getChangeHandler}
|
||||
getDescription={(item: Alert) => moment(item.started_at).format('MMM DD, YYYY hh:mm A')}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { ChangeEvent, useCallback, useState } from 'react';
|
||||
|
||||
import { Card, EmptySearchResult, HorizontalGroup, Input, Modal, VerticalGroup, Tag } from '@grafana/ui';
|
||||
import { EmptySearchResult, HorizontalGroup, Input, Modal, VerticalGroup, Tag } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ const CreateAlertReceiveChannelContainer = observer((props: CreateAlertReceiveCh
|
|||
</div>
|
||||
<div className={cx('cards', { cards_centered: !options.length })}>
|
||||
{options.length ? (
|
||||
options.map((alertReceiveChannelChoice, index) => {
|
||||
options.map((alertReceiveChannelChoice) => {
|
||||
return (
|
||||
<Block
|
||||
bordered
|
||||
|
|
@ -95,13 +95,6 @@ const CreateAlertReceiveChannelContainer = observer((props: CreateAlertReceiveCh
|
|||
<EmptySearchResult>Could not find anything matching your query</EmptySearchResult>
|
||||
)}
|
||||
</div>
|
||||
{/* Need to add documentation link to Integrations
|
||||
<div className={cx('footer')}>
|
||||
<Text type="secondary"> Find out how integrations work: </Text>
|
||||
<a target="_blank" href="https://grafana.com/docs/">
|
||||
Read documentation
|
||||
</a>
|
||||
</div> */}
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export function getSlackMessage(slackError: SlackError, team: Team) {
|
|||
|
||||
if (slackError === SlackError.USER_ALREADY_CONNECTED) {
|
||||
return (
|
||||
<>Couldn’t connect to Slack. This Slack account has already been connected to another user in this organization</>
|
||||
<>Couldn't connect to Slack. This Slack account has already been connected to another user in this organization</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import React from 'react';
|
||||
|
||||
import { User } from 'models/user/user.types';
|
||||
|
||||
export const getIfChatOpsConnected = (user: User) => {
|
||||
|
|
|
|||
|
|
@ -49,22 +49,6 @@ const EscalationChainCard = observer((props: AlertReceiveChannelCardProps) => {
|
|||
</Tooltip>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
{/*<HorizontalGroup>
|
||||
<PluginLink
|
||||
query={{ page: 'incidents', integration: alertReceiveChannel.id }}
|
||||
className={cx('alertsInfoText')}
|
||||
>
|
||||
<b>{alertReceiveChannel.alert_count}</b> alerts in <b>{alertReceiveChannel.alert_groups_count}</b>{' '}
|
||||
incidents
|
||||
</PluginLink>
|
||||
<Text type="secondary" size="small">
|
||||
|
|
||||
</Text>
|
||||
<IntegrationLogo scale={0.08} integration={integration} />
|
||||
<Text type="secondary" size="small">
|
||||
{integration?.display_name}
|
||||
</Text>
|
||||
</HorizontalGroup>*/}
|
||||
</VerticalGroup>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import React, { ChangeEvent, FC, useCallback, useState } from 'react';
|
|||
import { Button, Field, HorizontalGroup, Input, Modal } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import { ChannelFilter } from 'models/channel_filter/channel_filter.types';
|
||||
import { EscalationChain } from 'models/escalation_chain/escalation_chain.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
|
|
|
|||
|
|
@ -62,13 +62,10 @@ const EscalationChainSteps = observer((props: EscalationChainStepsProps) => {
|
|||
{addonBefore}
|
||||
{escalationPolicyIds ? (
|
||||
escalationPolicyIds.map((escalationPolicyId, index) => {
|
||||
// const COLOR_RED = '#FF0000';
|
||||
const COLOR_RED = '#E60000';
|
||||
// const STEP_COLORS = ['#52C41A', '#A0D911', '#FADB14', '#FAAD14', COLOR_RED];
|
||||
const STEP_COLORS = ['#1A7F4B', '#33cc33', '#ffbf00', '#FF8000', COLOR_RED];
|
||||
|
||||
const { escalationPolicyStore } = store;
|
||||
|
||||
const escalationPolicy = escalationPolicyStore.items[escalationPolicyId];
|
||||
|
||||
if (!escalationPolicy) {
|
||||
|
|
@ -102,7 +99,6 @@ const EscalationChainSteps = observer((props: EscalationChainStepsProps) => {
|
|||
menuShouldPortal
|
||||
placeholder="Add escalation step..."
|
||||
onChange={handleCreateEscalationStep}
|
||||
/* isOptionDisabled={(...rest) => console.log(rest)}*/
|
||||
options={escalationPolicyStore.webEscalationChoices.map((choice: EscalationPolicyOption) => ({
|
||||
value: choice.value,
|
||||
label: choice.create_display_name,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import React, { ReactElement, useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import React, { ReactElement, useCallback, useEffect } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select, MultiSelect, AsyncMultiSelect, AsyncSelect, Tooltip } from '@grafana/ui';
|
||||
import { AsyncMultiSelect, AsyncSelect } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { get, isNil } from 'lodash-es';
|
||||
import { observer } from 'mobx-react';
|
||||
import Emoji from 'react-emoji-render';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,10 @@ const GrafanaTeamSelect = observer((props: GrafanaTeamSelectProps) => {
|
|||
window.location.search = queryParams.toString();
|
||||
|
||||
function mapCurrentPage() {
|
||||
if (currentPage === 'incident') {return 'incidents'}
|
||||
return currentPage
|
||||
if (currentPage === 'incident') {
|
||||
return 'incidents';
|
||||
}
|
||||
return currentPage;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Button, HorizontalGroup, Modal, Select } from '@grafana/ui';
|
||||
import { Button, HorizontalGroup, Select } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import Emoji from 'react-emoji-render';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import { capitalCase } from 'change-case';
|
||||
import qs from 'query-string';
|
||||
|
||||
import { convertRelativeToAbsoluteDate } from 'utils/datetime';
|
||||
|
||||
import { FilterOption } from './IncidentFilters.types';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { IncidentStatus } from 'models/alertgroup/alertgroup.types';
|
||||
import { SelectOption } from 'state/types';
|
||||
|
||||
export interface IncidentsFiltersType {}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ class IncidentsFilters extends Component<IncidentsFiltersProps, IncidentsFilters
|
|||
return (
|
||||
<div className={cx('filters')}>
|
||||
{filters.map((filterOption: FilterOption) => (
|
||||
<div className={cx('filter')}>
|
||||
<div key={filterOption.name} className={cx('filter')}>
|
||||
<Text type="secondary">{capitalCase(filterOption.name)}:</Text> {this.renderFilterOption(filterOption)}
|
||||
<IconButton size="sm" name="times" onClick={this.getDeleteFilterClickHandler(filterOption.name)} />
|
||||
</div>
|
||||
|
|
@ -130,24 +130,14 @@ class IncidentsFilters extends Component<IncidentsFiltersProps, IncidentsFilters
|
|||
|
||||
renderCards() {
|
||||
const { store } = this.props;
|
||||
const {
|
||||
teamStore: { currentTeam },
|
||||
} = store;
|
||||
|
||||
const { values } = this.state;
|
||||
|
||||
const { newIncidents, acknowledgedIncidents, resolvedIncidents, silencedIncidents } = store.alertGroupStore;
|
||||
|
||||
const { count: newIncidentsCount, alert_group_rate_to_previous_same_period: newIncidentsRate } = newIncidents;
|
||||
|
||||
const { count: acknowledgedIncidentsCount, alert_group_rate_to_previous_same_period: acknowledgedIncidentsRate } =
|
||||
acknowledgedIncidents;
|
||||
|
||||
const { count: resolvedIncidentsCount, alert_group_rate_to_previous_same_period: resolvedIncidentsRate } =
|
||||
resolvedIncidents;
|
||||
|
||||
const { count: silencedIncidentsCount, alert_group_rate_to_previous_same_period: silencedIncidentsRate } =
|
||||
silencedIncidents;
|
||||
const { count: newIncidentsCount } = newIncidents;
|
||||
const { count: acknowledgedIncidentsCount } = acknowledgedIncidents;
|
||||
const { count: resolvedIncidentsCount } = resolvedIncidents;
|
||||
const { count: silencedIncidentsCount } = silencedIncidents;
|
||||
|
||||
const status = values.status || [];
|
||||
|
||||
|
|
@ -194,7 +184,7 @@ class IncidentsFilters extends Component<IncidentsFiltersProps, IncidentsFilters
|
|||
}
|
||||
|
||||
handleSearch = (query: string) => {
|
||||
const { filters, values } = this.state;
|
||||
const { filters } = this.state;
|
||||
|
||||
const searchFilter = filters.find((filter: FilterOption) => filter.name === 'search');
|
||||
|
||||
|
|
@ -217,7 +207,7 @@ class IncidentsFilters extends Component<IncidentsFiltersProps, IncidentsFilters
|
|||
};
|
||||
|
||||
getDeleteFilterClickHandler = (filterName: FilterOption['name']) => {
|
||||
const { filters, values } = this.state;
|
||||
const { filters } = this.state;
|
||||
|
||||
return () => {
|
||||
const newFilters = filters.filter((filterOption: FilterOption) => filterOption.name !== filterName);
|
||||
|
|
@ -306,11 +296,6 @@ class IncidentsFilters extends Component<IncidentsFiltersProps, IncidentsFilters
|
|||
const value = {
|
||||
from: dates ? moment(dates[0] + 'Z') : undefined,
|
||||
to: dates ? moment(dates[1] + 'Z') : undefined,
|
||||
/* raw: {
|
||||
from: dates ? moment(dates[0]).format('MMM DD, YYYY hh:mm A') : undefined,
|
||||
to: dates ? moment(dates[1]).format('MMM DD, YYYY hh:mm A') : undefined,
|
||||
},*/
|
||||
|
||||
raw: {
|
||||
from: dates ? dates[0] : '',
|
||||
to: dates ? dates[1] : '',
|
||||
|
|
@ -378,7 +363,7 @@ class IncidentsFilters extends Component<IncidentsFiltersProps, IncidentsFilters
|
|||
};
|
||||
|
||||
getRemoteOptionsChangeHandler = (name: FilterOption['name']) => {
|
||||
return (value: SelectableValue[], items: any[]) => {
|
||||
return (value: SelectableValue[], _items: any[]) => {
|
||||
this.onFiltersValueChange(name, value);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,17 +1,7 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { getLocationSrv, setLocationSrv } from '@grafana/runtime';
|
||||
import {
|
||||
Drawer,
|
||||
Tab,
|
||||
TabContent,
|
||||
TabsBar,
|
||||
IconButton,
|
||||
Button,
|
||||
HorizontalGroup,
|
||||
VerticalGroup,
|
||||
Input,
|
||||
} from '@grafana/ui';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
import { Drawer, Tab, TabContent, TabsBar, Button, VerticalGroup, Input } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
|
|
@ -21,16 +11,13 @@ import IntegrationLogo from 'components/IntegrationLogo/IntegrationLogo';
|
|||
import Text from 'components/Text/Text';
|
||||
import AlertTemplatesFormContainer from 'containers/AlertTemplatesFormContainer/AlertTemplatesFormContainer';
|
||||
import HeartbeatForm from 'containers/HeartbeatModal/HeartbeatForm';
|
||||
import { UserSettingsTab } from 'containers/UserSettings/UserSettings.types';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { Alert } from 'models/alertgroup/alertgroup.types';
|
||||
import { SelectOption } from 'state/types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { openNotification } from 'utils';
|
||||
|
||||
import { IntegrationSettingsTab } from './IntegrationSettings.types';
|
||||
import Autoresolve from './parts/Autoresolve';
|
||||
import LiveLogs from './parts/LiveLogs';
|
||||
|
||||
import styles from 'containers/IntegrationSettings/IntegrationSettings.module.css';
|
||||
|
||||
|
|
@ -74,7 +61,7 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
|
|||
|
||||
const integration = alertReceiveChannelStore.getIntegration(alertReceiveChannel);
|
||||
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [expanded, _setExpanded] = useState(false);
|
||||
|
||||
const handleSwitchToTemplate = (templateName: string) => {
|
||||
setSelectedTemplate(templateName);
|
||||
|
|
@ -85,34 +72,17 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
|
|||
scrollableContent
|
||||
expandable
|
||||
title={
|
||||
<>
|
||||
{/*<div className={cx('settings-header-buttons')}>
|
||||
<IconButton // OMG it blocks 'how to' instructions text selection to copy
|
||||
name={expanded ? 'angle-right' : 'angle-left'}
|
||||
size="xl"
|
||||
surface="header"
|
||||
onClick={() => {
|
||||
if (expanded) {
|
||||
setExpanded(false);
|
||||
} else {
|
||||
setExpanded(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<IconButton name="times" size="xl" surface="header" onClick={onHide} />
|
||||
</div>*/}
|
||||
<div className={cx('title')}>
|
||||
{integration && <IntegrationLogo integration={integration} scale={0.2} />}
|
||||
<div className={cx('title-column')}>
|
||||
{alertReceiveChannel && (
|
||||
<Text.Title level={4}>
|
||||
<Emoji text={alertReceiveChannel.verbal_name} /> settings
|
||||
</Text.Title>
|
||||
)}
|
||||
{integration && <Text type="secondary">Type: {integration.display_name}</Text>}
|
||||
</div>
|
||||
<div className={cx('title')}>
|
||||
{integration && <IntegrationLogo integration={integration} scale={0.2} />}
|
||||
<div className={cx('title-column')}>
|
||||
{alertReceiveChannel && (
|
||||
<Text.Title level={4}>
|
||||
<Emoji text={alertReceiveChannel.verbal_name} /> settings
|
||||
</Text.Title>
|
||||
)}
|
||||
{integration && <Text type="secondary">Type: {integration.display_name}</Text>}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
width={expanded ? '100%' : '70%'}
|
||||
onClose={onHide}
|
||||
|
|
@ -144,14 +114,6 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
|
|||
key={IntegrationSettingsTab.Autoresolve}
|
||||
onChangeTab={getTabClickHandler(IntegrationSettingsTab.Autoresolve)}
|
||||
/>
|
||||
|
||||
{/* Removed untill backend is ready
|
||||
<Tab
|
||||
active={activeTab === IntegrationSettingsTab.LiveLogs}
|
||||
label="Live Logs"
|
||||
key={IntegrationSettingsTab.LiveLogs}
|
||||
onChangeTab={getTabClickHandler(IntegrationSettingsTab.LiveLogs)}
|
||||
/> */}
|
||||
</TabsBar>
|
||||
<TabContent className={cx('content')}>
|
||||
{activeTab === IntegrationSettingsTab.Templates && (
|
||||
|
|
@ -176,7 +138,6 @@ const IntegrationSettings = observer((props: IntegrationSettingsProps) => {
|
|||
alertGroupId={alertGroupId}
|
||||
/>
|
||||
)}
|
||||
{/*{activeTab === IntegrationSettingsTab.LiveLogs && <LiveLogs alertReceiveChannelId={id} />}*/}
|
||||
{activeTab === IntegrationSettingsTab.HowToConnect && (
|
||||
<div className="container">
|
||||
<VerticalGroup>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import cn from 'classnames/bind';
|
|||
import { get } from 'lodash-es';
|
||||
|
||||
import Block from 'components/GBlock/Block';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import Text from 'components/Text/Text';
|
||||
import GSelect from 'containers/GSelect/GSelect';
|
||||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
|
|
@ -55,8 +54,7 @@ const Autoresolve = ({ alertReceiveChannelId, onSwitchToTemplate, alertGroupId }
|
|||
store.alertReceiveChannelStore.templates[alertReceiveChannelId],
|
||||
'resolve_condition_template'
|
||||
);
|
||||
// @ts-ignore
|
||||
if (autoresolveCondition == ['invalid template']) {
|
||||
if (autoresolveCondition === 'invalid template') {
|
||||
setAutoresolveConditionInvalid(true);
|
||||
}
|
||||
}, [store.alertReceiveChannelStore.templates[alertReceiveChannelId]]);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Alert, Button, EmptySearchResult, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { Button, EmptySearchResult, LoadingPlaceholder } from '@grafana/ui';
|
||||
|
||||
import SourceCode from 'components/SourceCode/SourceCode';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactElement } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import Emoji from 'react-emoji-render';
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
import { Maintenance } from 'models/maintenance/maintenance.types';
|
||||
import { Schedule } from 'models/schedule/schedule.types';
|
||||
|
||||
export function prepareForEdit(item: Maintenance) {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
import React, { HTMLAttributes, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { HTMLAttributes, useEffect, useState } from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, Icon, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { Button, LoadingPlaceholder } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import GTable from 'components/GTable/GTable';
|
||||
import Text from 'components/Text/Text';
|
||||
import { UserSettingsTab } from 'containers/UserSettings/UserSettings.types';
|
||||
import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl';
|
||||
import { User } from 'models/user/user.types';
|
||||
import { makeRequest } from 'network';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { UserAction } from 'state/userAction';
|
||||
|
||||
|
|
@ -33,8 +30,6 @@ const MobileAppVerification = observer((props: MobileAppVerificationProps) => {
|
|||
const isCurrent = userStore.currentUserPk === user.pk;
|
||||
const action = isCurrent ? UserAction.UpdateOwnSettings : UserAction.UpdateOtherUsersSettings;
|
||||
|
||||
const { id = UserSettingsTab.UserInfo } = props;
|
||||
|
||||
const [showMobileAppVerificationToken, setShowMobileAppVerificationToken] = useState<string>(undefined);
|
||||
const [isMobileAppVerificationTokenExisting, setIsMobileAppVerificationTokenExisting] = useState<boolean>(false);
|
||||
const [MobileAppVerificationTokenLoading, setMobileAppVerificationTokenLoading] = useState<boolean>(true);
|
||||
|
|
@ -42,11 +37,11 @@ const MobileAppVerification = observer((props: MobileAppVerificationProps) => {
|
|||
useEffect(() => {
|
||||
userStore
|
||||
.getMobileAppVerificationToken(userPk)
|
||||
.then((res) => {
|
||||
.then((_res) => {
|
||||
setIsMobileAppVerificationTokenExisting(true);
|
||||
setMobileAppVerificationTokenLoading(false);
|
||||
})
|
||||
.catch((res) => {
|
||||
.catch((_res) => {
|
||||
setIsMobileAppVerificationTokenExisting(false);
|
||||
setMobileAppVerificationTokenLoading(false);
|
||||
});
|
||||
|
|
@ -59,27 +54,6 @@ const MobileAppVerification = observer((props: MobileAppVerificationProps) => {
|
|||
.then((res) => setShowMobileAppVerificationToken(res?.token));
|
||||
};
|
||||
|
||||
// const [devices, setDevices] = useState();
|
||||
//
|
||||
// const updateDevices = useCallback(() => {
|
||||
// makeRequest(`/device/apns/`, {
|
||||
// method: 'GET',
|
||||
// }).then((data) => {
|
||||
// setDevices(data);
|
||||
// });
|
||||
// }, []);
|
||||
//
|
||||
// useEffect(() => {
|
||||
// updateDevices();
|
||||
// }, []);
|
||||
//
|
||||
// const columns = [
|
||||
// {
|
||||
// title: 'Name',
|
||||
// dataIndex: 'name',
|
||||
// },
|
||||
// ];
|
||||
|
||||
return (
|
||||
<div className={cx('mobile-app-settings')}>
|
||||
{MobileAppVerificationTokenLoading ? (
|
||||
|
|
@ -114,41 +88,23 @@ const MobileAppVerification = observer((props: MobileAppVerificationProps) => {
|
|||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
<WithPermissionControl userAction={action}>
|
||||
<Button
|
||||
onClick={handleCreateMobileAppVerificationToken}
|
||||
className={cx('iCal-button')}
|
||||
variant="secondary"
|
||||
>
|
||||
Get the code
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</p>
|
||||
</>
|
||||
<p>
|
||||
<WithPermissionControl userAction={action}>
|
||||
<Button
|
||||
onClick={handleCreateMobileAppVerificationToken}
|
||||
className={cx('iCal-button')}
|
||||
variant="secondary"
|
||||
>
|
||||
Get the code
|
||||
</Button>
|
||||
</WithPermissionControl>
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<Text>* Only iOS is currently supported</Text>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{/*<>*/}
|
||||
{/* <GTable*/}
|
||||
{/* title={() => (*/}
|
||||
{/* <div className={cx('header')}>*/}
|
||||
{/* <HorizontalGroup align="flex-end">*/}
|
||||
{/* <Text.Title level={4}>Your devices</Text.Title>*/}
|
||||
{/* </HorizontalGroup>*/}
|
||||
{/* </div>*/}
|
||||
{/* )}*/}
|
||||
{/* rowKey="id"*/}
|
||||
{/* className="api-keys"*/}
|
||||
{/* data={devices}*/}
|
||||
{/* emptyText={devices ? 'No devices connected' : 'Loading...'}*/}
|
||||
{/* columns={columns}*/}
|
||||
{/* />*/}
|
||||
{/*</>*/}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import React, { ChangeEvent, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { RawTimeRange } from '@grafana/data';
|
||||
import { Button, HorizontalGroup, Input, TimeRangeInput } from '@grafana/ui';
|
||||
import { HorizontalGroup, Input, TimeRangeInput } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import RemoteSelect from 'containers/RemoteSelect/RemoteSelect';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from './OrganizationLogFilters.module.css';
|
||||
|
||||
|
|
@ -23,8 +22,6 @@ const OrganizationLogFilters = observer((props: OrganizationLogFiltersProps) =>
|
|||
|
||||
const [createAtRaw, setCreateAtRaw] = useState<RawTimeRange>();
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const onSearchTermChangeCallback = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const filters = {
|
||||
|
|
@ -46,10 +43,6 @@ const OrganizationLogFilters = observer((props: OrganizationLogFiltersProps) =>
|
|||
};
|
||||
};
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
onChange({});
|
||||
}, [onChange]);
|
||||
|
||||
const handleChangeCreatedAt = useCallback(
|
||||
(filter) => {
|
||||
onChange({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import { ReactElement } from 'react';
|
||||
|
||||
import { FormItem, FormItemType } from 'components/GForm/GForm.types';
|
||||
import { DEFAULT_USER_ROLES } from 'models/user/user.config';
|
||||
|
||||
export const form: { name: string; fields: FormItem[] } = {
|
||||
name: 'OutgoingWebhook',
|
||||
|
|
|
|||
|
|
@ -2,16 +2,8 @@ import React, { useCallback, useEffect, useState } from 'react';
|
|||
|
||||
import { AppPluginMeta, PluginConfigPageProps } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import {
|
||||
Button,
|
||||
Field,
|
||||
HorizontalGroup,
|
||||
VerticalGroup,
|
||||
Input,
|
||||
Label,
|
||||
Legend,
|
||||
LoadingPlaceholder,
|
||||
} from '@grafana/ui';
|
||||
import { Button, Field, HorizontalGroup, VerticalGroup, Input, Label, Legend, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { AxiosError } from 'axios';
|
||||
import cn from 'classnames/bind';
|
||||
import { OnCallAppSettings } from 'types';
|
||||
|
||||
|
|
@ -23,9 +15,10 @@ import { makeRequest } from 'network';
|
|||
import {
|
||||
createGrafanaToken,
|
||||
getPluginSyncStatus,
|
||||
startPluginSync, SYNC_STATUS_RETRY_LIMIT,
|
||||
startPluginSync,
|
||||
SYNC_STATUS_RETRY_LIMIT,
|
||||
syncStatusDelay,
|
||||
updateGrafanaToken
|
||||
updateGrafanaToken,
|
||||
} from 'state/plugin';
|
||||
import { GRAFANA_LICENSE_OSS } from 'utils/consts';
|
||||
import { getItem, setItem } from 'utils/localStorage';
|
||||
|
|
@ -49,7 +42,9 @@ export const PluginConfigPage = (props: Props) => {
|
|||
const [isSelfHostedInstall, setIsSelfHostedInstall] = useState<boolean>(true);
|
||||
const [retrySync, setRetrySync] = useState<boolean>(false);
|
||||
|
||||
const INVALID_INVITE_TOKEN_ERROR_MSG = `It seems like your invite token may be invalid. ${constructErrorActionMessage('generating a new invite token')}`;
|
||||
const INVALID_INVITE_TOKEN_ERROR_MSG = `It seems like your invite token may be invalid. ${constructErrorActionMessage(
|
||||
'generating a new invite token'
|
||||
)}`;
|
||||
|
||||
const setupPlugin = useCallback(async () => {
|
||||
setItem('onCallApiUrl', onCallApiUrl);
|
||||
|
|
@ -134,23 +129,30 @@ export const PluginConfigPage = (props: Props) => {
|
|||
setGrafanaUrl(e.target.value);
|
||||
}, []);
|
||||
|
||||
const handleSyncException = useCallback((e) => {
|
||||
const buildErrMsg = (msg: string): string =>
|
||||
constructSyncErrorMessage(msg, plugin.meta.jsonData?.onCallApiUrl);
|
||||
const handleSyncException = useCallback((e: AxiosError) => {
|
||||
const buildErrMsg = (msg: string): string => constructSyncErrorMessage(msg, plugin.meta.jsonData?.onCallApiUrl);
|
||||
|
||||
if (plugin.meta.jsonData?.onCallApiUrl) {
|
||||
const { status: statusCode } = e.response;
|
||||
const { status: statusCode } = e.response;
|
||||
|
||||
let statusMessage: string;
|
||||
|
||||
if (statusCode == 403) {
|
||||
if (statusCode === 403) {
|
||||
statusMessage = buildErrMsg(INVALID_INVITE_TOKEN_ERROR_MSG);
|
||||
} else if (statusCode === 404) {
|
||||
statusMessage = buildErrMsg('If Grafana OnCall was just installed, restart Grafana for OnCall routes to be available.');
|
||||
statusMessage = buildErrMsg(
|
||||
'If Grafana OnCall was just installed, restart Grafana for OnCall routes to be available.'
|
||||
);
|
||||
} else if (statusCode === 502) {
|
||||
statusMessage = buildErrMsg(`Unable to communicate with either the Grafana API, or Grafana OnCall engine API. ${constructErrorActionMessage('verify that the API URLs that you entered are correct')}`);
|
||||
statusMessage = buildErrMsg(
|
||||
`Unable to communicate with either the Grafana API, or Grafana OnCall engine API. ${constructErrorActionMessage(
|
||||
'verify that the API URLs that you entered are correct'
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
statusMessage = buildErrMsg(`An unknown error occured. ${constructErrorActionMessage()}. If the error still occurs please reach out to support.`)
|
||||
statusMessage = buildErrMsg(
|
||||
`An unknown error occured. ${constructErrorActionMessage()}. If the error still occurs please reach out to support.`
|
||||
);
|
||||
}
|
||||
setPluginStatusMessage(statusMessage);
|
||||
setRetrySync(true);
|
||||
|
|
@ -168,17 +170,18 @@ export const PluginConfigPage = (props: Props) => {
|
|||
? ` (${getSyncResponse.license}, ${getSyncResponse.version})`
|
||||
: '';
|
||||
|
||||
let pluginStatusMessage = `Connected to OnCall${versionInfo}\n - OnCall URL: ${plugin.meta.jsonData.onCallApiUrl}\n`
|
||||
let pluginStatusMessage = `Connected to OnCall${versionInfo}\n - OnCall URL: ${plugin.meta.jsonData.onCallApiUrl}\n`;
|
||||
if (plugin.meta.jsonData.grafanaUrl) {
|
||||
pluginStatusMessage = `${pluginStatusMessage} - Grafana URL: ${plugin.meta.jsonData.grafanaUrl}`
|
||||
pluginStatusMessage = `${pluginStatusMessage} - Grafana URL: ${plugin.meta.jsonData.grafanaUrl}`;
|
||||
}
|
||||
|
||||
setPluginStatusMessage(pluginStatusMessage)
|
||||
setPluginStatusMessage(pluginStatusMessage);
|
||||
setIsSelfHostedInstall(plugin.meta.jsonData?.license === GRAFANA_LICENSE_OSS);
|
||||
setPluginStatusOk(true);
|
||||
} else {
|
||||
setPluginStatusMessage(constructSyncErrorMessage(INVALID_INVITE_TOKEN_ERROR_MSG,
|
||||
plugin.meta.jsonData.grafanaUrl));
|
||||
setPluginStatusMessage(
|
||||
constructSyncErrorMessage(INVALID_INVITE_TOKEN_ERROR_MSG, plugin.meta.jsonData.grafanaUrl)
|
||||
);
|
||||
setRetrySync(true);
|
||||
}
|
||||
setPluginConfigLoading(false);
|
||||
|
|
@ -195,16 +198,16 @@ export const PluginConfigPage = (props: Props) => {
|
|||
return;
|
||||
}
|
||||
|
||||
getPluginSyncStatus().then((get_sync_response) => {
|
||||
if (get_sync_response.hasOwnProperty('token_ok')) {
|
||||
finishSync(get_sync_response);
|
||||
} else {
|
||||
syncStatusDelay(retryCount + 1).then(() => waitForSyncStatus(retryCount + 1))
|
||||
}
|
||||
}).catch((e) => {
|
||||
handleSyncException(e);
|
||||
});
|
||||
}
|
||||
getPluginSyncStatus()
|
||||
.then((get_sync_response) => {
|
||||
if (get_sync_response.hasOwnProperty('token_ok')) {
|
||||
finishSync(get_sync_response);
|
||||
} else {
|
||||
syncStatusDelay(retryCount + 1).then(() => waitForSyncStatus(retryCount + 1));
|
||||
}
|
||||
})
|
||||
.catch(handleSyncException);
|
||||
};
|
||||
|
||||
const startSync = useCallback(() => {
|
||||
setRetrySync(false);
|
||||
|
|
@ -214,9 +217,7 @@ export const PluginConfigPage = (props: Props) => {
|
|||
.catch(handleSyncException);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
startSync();
|
||||
}, []);
|
||||
useEffect(startSync, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
export const constructSyncErrorMessage = (errMsg: string, url?: string): string =>
|
||||
`${url ? `${url}\n` : ''}${errMsg}`;
|
||||
export const constructSyncErrorMessage = (errMsg: string, url?: string): string => `${url ? `${url}\n` : ''}${errMsg}`;
|
||||
|
||||
export const constructErrorActionMessage = (msg?: string): string =>
|
||||
`Try removing your current configuration, ${msg ? msg : 'double checking your settings'}, and re-initializing the plugin.\nBy removing your current configuration, you will need to ensure that you regenerate a new invite token, and input this in your new configuration.`
|
||||
`Try removing your current configuration, ${
|
||||
msg ? msg : 'double checking your settings'
|
||||
}, and re-initializing the plugin.\nBy removing your current configuration, you will need to ensure that you regenerate a new invite token, and input this in your new configuration.`;
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
.root {
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -2,15 +2,10 @@ import React, { useCallback, useEffect, useMemo, useReducer } from 'react';
|
|||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { AsyncMultiSelect, AsyncSelect } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
|
||||
import { makeRequest } from 'network';
|
||||
|
||||
import styles from './RemoteSelect.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface RemoteSelectProps {
|
||||
autoFocus?: boolean;
|
||||
href: string;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import React, { FC, useMemo, useState, useEffect, useRef, useCallback } from 'react';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
|
||||
import { HorizontalGroup, LoadingPlaceholder } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import ScheduleSlot from 'containers/ScheduleSlot/ScheduleSlot';
|
||||
import { getFromString } from 'models/schedule/schedule.helpers';
|
||||
import { Rotation as RotationType, Schedule, Event } from 'models/schedule/schedule.types';
|
||||
import { Schedule, Event } from 'models/schedule/schedule.types';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { usePrevious } from 'utils/hooks';
|
||||
|
||||
import { getLabel } from './Rotation.helpers';
|
||||
|
||||
|
|
@ -16,8 +14,6 @@ import styles from './Rotation.module.css';
|
|||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface ScheduleSlotState {}
|
||||
|
||||
interface RotationProps {
|
||||
scheduleId: Schedule['id'];
|
||||
startMoment: dayjs.Dayjs;
|
||||
|
|
@ -45,38 +41,7 @@ const Rotation: FC<RotationProps> = (props) => {
|
|||
transparent = false,
|
||||
} = props;
|
||||
|
||||
const [animate, setAnimate] = useState<boolean>(true);
|
||||
const [width, setWidth] = useState<number | undefined>();
|
||||
|
||||
const startMomentString = useMemo(() => getFromString(startMoment), [startMoment]);
|
||||
|
||||
const prevStartMomentString = usePrevious(startMomentString);
|
||||
|
||||
// console.log(events);
|
||||
|
||||
// const rotation = store.scheduleStore.rotations[id]?.[prevStartMomentString];
|
||||
|
||||
/* useEffect(() => {
|
||||
setTransparent(false);
|
||||
}, [rotation]);
|
||||
|
||||
useEffect(() => {
|
||||
setTransparent(true);
|
||||
}, [startMoment]);*/
|
||||
|
||||
useEffect(() => {
|
||||
const startMomentString = startMoment.utc().format('YYYY-MM-DDTHH:mm:ss.000Z');
|
||||
|
||||
// console.log('CHANGE START MOMENT', startMomentString);
|
||||
|
||||
// store.scheduleStore.updateEvents(scheduleId, startMomentString, currentTimezone);
|
||||
}, [startMomentString]);
|
||||
|
||||
const slots = useCallback((node) => {
|
||||
if (node) {
|
||||
setWidth(node.offsetWidth);
|
||||
}
|
||||
}, []);
|
||||
const [animate, _setAnimate] = useState<boolean>(true);
|
||||
|
||||
const handleClick = (event) => {
|
||||
const rect = event.currentTarget.getBoundingClientRect();
|
||||
|
|
@ -94,11 +59,8 @@ const Rotation: FC<RotationProps> = (props) => {
|
|||
}
|
||||
|
||||
const firstShift = events[0];
|
||||
|
||||
const firstShiftOffset = dayjs(firstShift.start).diff(startMoment, 'seconds');
|
||||
|
||||
const base = 60 * 60 * 24 * days;
|
||||
// const utcOffset = dayjs().tz(currentTimezone).utcOffset();
|
||||
|
||||
return firstShiftOffset / base;
|
||||
}, [events]);
|
||||
|
|
|
|||
|
|
@ -1,20 +1,10 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { DateTime, dateTime } from '@grafana/data';
|
||||
import { DatePickerWithInput, HorizontalGroup, TimeOfDayPicker, Tooltip } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { DatePickerWithInput, HorizontalGroup, TimeOfDayPicker } from '@grafana/ui';
|
||||
import dayjs from 'dayjs';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Moment } from 'moment-timezone';
|
||||
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { getUserNotificationsSummary } from 'models/user/user.helpers';
|
||||
import { User } from 'models/user/user.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from 'containers/UserTooltip/UserTooltip.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface UserTooltipProps {
|
||||
value: dayjs.Dayjs;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { dateTime, DateTime } from '@grafana/data';
|
||||
import {
|
||||
IconButton,
|
||||
VerticalGroup,
|
||||
HorizontalGroup,
|
||||
Field,
|
||||
Input,
|
||||
Button,
|
||||
Select,
|
||||
InlineSwitch,
|
||||
DatePickerWithInput,
|
||||
TimeOfDayPicker,
|
||||
} from '@grafana/ui';
|
||||
import { IconButton, VerticalGroup, HorizontalGroup, Field, Button, Select, InlineSwitch } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -21,16 +9,14 @@ import Draggable from 'react-draggable';
|
|||
import Modal from 'components/Modal/Modal';
|
||||
import Text from 'components/Text/Text';
|
||||
import UserGroups from 'components/UserGroups/UserGroups';
|
||||
import { Item } from 'components/UserGroups/UserGroups.types';
|
||||
import WithConfirm from 'components/WithConfirm/WithConfirm';
|
||||
import WorkingHours from 'components/WorkingHours/WorkingHours';
|
||||
import RemoteSelect from 'containers/RemoteSelect/RemoteSelect';
|
||||
import { getFromString } from 'models/schedule/schedule.helpers';
|
||||
import { Rotation, Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { getTzOffsetString } from 'models/timezone/timezone.helpers';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { User } from 'models/user/user.types';
|
||||
import { makeRequest } from 'network';
|
||||
import { getDateTime, getUTCString } from 'pages/schedule/Schedule.helpers';
|
||||
import { SelectOption } from 'state/types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
|
@ -38,7 +24,6 @@ import { getCoords, waitForElement } from 'utils/DOM';
|
|||
import { useDebouncedCallback } from 'utils/hooks';
|
||||
|
||||
import DateTimePicker from './DateTimePicker';
|
||||
import { RotationCreateData } from './RotationForm.types';
|
||||
|
||||
import styles from './RotationForm.module.css';
|
||||
|
||||
|
|
@ -77,10 +62,8 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
shiftColor = '#3D71D9',
|
||||
} = props;
|
||||
|
||||
// console.log('shiftColor', shiftColor);
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const [offsetTop, setOffsetTop] = useState<number>(0);
|
||||
const [repeatEveryValue, setRepeatEveryValue] = useState<number>(1);
|
||||
const [repeatEveryPeriod, setRepeatEveryPeriod] = useState<number>(0);
|
||||
const [selectedDays, setSelectedDays] = useState<string[]>([]);
|
||||
|
|
@ -90,6 +73,9 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
const [endLess, setEndless] = useState<boolean>(true);
|
||||
const [rotationEnd, setRotationEnd] = useState<dayjs.Dayjs>(shiftMoment.add(1, 'month'));
|
||||
|
||||
const store = useStore();
|
||||
const shift = store.scheduleStore.shifts[shiftId];
|
||||
|
||||
useEffect(() => {
|
||||
if (rotationStart.isBefore(shiftStart)) {
|
||||
setRotationStart(shiftStart);
|
||||
|
|
@ -106,22 +92,12 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
[shiftStart, shiftEnd]
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const shift = store.scheduleStore.shifts[shiftId];
|
||||
|
||||
const [offsetTop, setOffsetTop] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
waitForElement(`#layer${shiftId === 'new' ? layerPriority : shift?.priority_level}`).then((elm) => {
|
||||
const modal = document.querySelector(`.${cx('draggable')}`) as HTMLDivElement;
|
||||
|
||||
const coords = getCoords(elm);
|
||||
|
||||
// elm.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
|
||||
// setOffsetTop(Math.max(coords.top + elm.offsetHeight, 0));
|
||||
|
||||
const offsetTop = Math.min(
|
||||
Math.max(coords.top - modal?.offsetHeight - 10, 10),
|
||||
document.body.offsetHeight - modal?.offsetHeight - 10
|
||||
|
|
@ -277,8 +253,6 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
</HorizontalGroup>
|
||||
</Text>
|
||||
<HorizontalGroup>
|
||||
{/*<IconButton disabled variant="secondary" tooltip="Copy" name="copy" />
|
||||
<IconButton disabled variant="secondary" tooltip="Code" name="brackets-curly" />*/}
|
||||
{shiftId !== 'new' && (
|
||||
<WithConfirm>
|
||||
<IconButton variant="secondary" tooltip="Delete" name="trash-alt" onClick={handleDeleteClick} />
|
||||
|
|
@ -287,7 +261,6 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
<IconButton variant="secondary" className={cx('drag-handler')} name="draggabledots" />
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
{/*<hr />*/}
|
||||
<div className={cx('content')}>
|
||||
<VerticalGroup>
|
||||
<div className={cx('two-fields')}>
|
||||
|
|
@ -348,7 +321,6 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
</Field>
|
||||
</HorizontalGroup>
|
||||
{repeatEveryPeriod === 1 && (
|
||||
/*<HorizontalGroup justify="center">*/
|
||||
<Field label="Select days to repeat">
|
||||
<DaysSelector
|
||||
options={store.scheduleStore.byDayOptions}
|
||||
|
|
@ -356,7 +328,6 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
onChange={(value) => setSelectedDays(value)}
|
||||
/>
|
||||
</Field>
|
||||
/*</HorizontalGroup>*/
|
||||
)}
|
||||
<div className={cx('two-fields')}>
|
||||
<Field
|
||||
|
|
@ -429,6 +400,7 @@ const DaysSelector = ({ value, onChange, options }: DaysSelectorProps) => {
|
|||
<div className={cx('days')}>
|
||||
{options.map(({ display_name, value: itemValue }) => (
|
||||
<div
|
||||
key={display_name}
|
||||
onClick={getDayClickHandler(itemValue as string)}
|
||||
className={cx('day', { day__selected: value.includes(itemValue as string) })}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { dateTime, DateTime } from '@grafana/data';
|
||||
import { IconButton, VerticalGroup, HorizontalGroup, Field, Input, Button, Select, InlineSwitch } from '@grafana/ui';
|
||||
import { IconButton, VerticalGroup, HorizontalGroup, Field, Button } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import Draggable from 'react-draggable';
|
||||
|
|
@ -12,7 +11,7 @@ import UserGroups from 'components/UserGroups/UserGroups';
|
|||
import WithConfirm from 'components/WithConfirm/WithConfirm';
|
||||
import WorkingHours from 'components/WorkingHours/WorkingHours';
|
||||
import { getFromString } from 'models/schedule/schedule.helpers';
|
||||
import { Rotation, Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { getTzOffsetString } from 'models/timezone/timezone.helpers';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { User } from 'models/user/user.types';
|
||||
|
|
@ -22,7 +21,6 @@ import { getCoords, waitForElement } from 'utils/DOM';
|
|||
import { useDebouncedCallback } from 'utils/hooks';
|
||||
|
||||
import DateTimePicker from './DateTimePicker';
|
||||
import { RotationCreateData } from './RotationForm.types';
|
||||
|
||||
import styles from './RotationForm.module.css';
|
||||
|
||||
|
|
@ -189,8 +187,6 @@ const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
|
|||
<HorizontalGroup justify="space-between">
|
||||
<Text size="medium">{shiftId === 'new' ? 'New Override' : 'Update Override'}</Text>
|
||||
<HorizontalGroup>
|
||||
{/*<IconButton disabled variant="secondary" tooltip="Copy" name="copy" />
|
||||
<IconButton disabled variant="secondary" tooltip="Code" name="brackets-curly" />*/}
|
||||
{shiftId !== 'new' && (
|
||||
<WithConfirm>
|
||||
<IconButton variant="secondary" tooltip="Delete" name="trash-alt" onClick={handleDeleteClick} />
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ export const findColor = (shiftId: Shift['id'], layers: Layer[], overrides?) =>
|
|||
let layerIndex = -1;
|
||||
let rotationIndex = -1;
|
||||
if (layers) {
|
||||
outer: for (var i = 0; i < layers.length; i++) {
|
||||
for (var j = 0; j < layers[i].shifts.length; j++) {
|
||||
outer: for (let i = 0; i < layers.length; i++) {
|
||||
for (let j = 0; j < layers[i].shifts.length; j++) {
|
||||
const shift = layers[i].shifts[j];
|
||||
if (shift.shiftId === shiftId || (shiftId === 'new' && shift.isPreview)) {
|
||||
layerIndex = i;
|
||||
|
|
@ -21,7 +21,7 @@ export const findColor = (shiftId: Shift['id'], layers: Layer[], overrides?) =>
|
|||
|
||||
let overrideIndex = -1;
|
||||
if (layerIndex === -1 && rotationIndex === -1 && overrides) {
|
||||
for (var k = 0; k < overrides.length; k++) {
|
||||
for (let k = 0; k < overrides.length; k++) {
|
||||
const shift = overrides[k];
|
||||
if (shift.shiftId === shiftId || (shiftId === 'new' && shift.isPreview)) {
|
||||
overrideIndex = k;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import React, { Component, useMemo, useState } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { ValuePicker, IconButton, Icon, HorizontalGroup, Button, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { ValuePicker, HorizontalGroup, Button } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { toJS } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
|
||||
|
|
@ -13,11 +11,10 @@ import Text from 'components/Text/Text';
|
|||
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
|
||||
import Rotation from 'containers/Rotation/Rotation';
|
||||
import RotationForm from 'containers/RotationForm/RotationForm';
|
||||
import { RotationCreateData } from 'containers/RotationForm/RotationForm.types';
|
||||
import { getColor, getFromString } from 'models/schedule/schedule.helpers';
|
||||
import { Event, Layer, Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { Layer, Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { SelectOption, WithStoreProps } from 'state/types';
|
||||
import { WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
||||
import { DEFAULT_TRANSITION_TIMEOUT } from './Rotations.config';
|
||||
|
|
@ -122,7 +119,6 @@ class Rotations extends Component<RotationsProps, RotationsState> {
|
|||
<div className={cx('layer-title')}>
|
||||
<HorizontalGroup spacing="sm" justify="center">
|
||||
<Text type="secondary">Layer {layer.priority}</Text>
|
||||
{/*<Icon name="info-circle" />*/}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<div className={cx('rotations')}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component, useEffect } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, Icon, Input, ValuePicker } from '@grafana/ui';
|
||||
import { HorizontalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -9,8 +9,8 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
|||
import Text from 'components/Text/Text';
|
||||
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
|
||||
import Rotation from 'containers/Rotation/Rotation';
|
||||
import { getColor, getFromString, getLayersFromStore, getOverrideColor, getOverridesFromStore, getShiftsFromStore } from 'models/schedule/schedule.helpers';
|
||||
import { Event, Layer, Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { getLayersFromStore, getOverridesFromStore, getShiftsFromStore } from 'models/schedule/schedule.helpers';
|
||||
import { Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
|
@ -51,7 +51,7 @@ class ScheduleFinal extends Component<ScheduleFinalProps, ScheduleOverridesState
|
|||
const shifts = getShiftsFromStore(store, scheduleId, startMoment);
|
||||
|
||||
const layers = getLayersFromStore(store, scheduleId, startMoment);
|
||||
|
||||
|
||||
const overrides = getOverridesFromStore(store, scheduleId, startMoment);
|
||||
|
||||
const currentTimeHidden = currentTimeX < 0 || currentTimeX > 1;
|
||||
|
|
@ -67,12 +67,6 @@ class ScheduleFinal extends Component<ScheduleFinalProps, ScheduleOverridesState
|
|||
Final schedule
|
||||
</Text.Title>
|
||||
</div>
|
||||
{/*<Input
|
||||
prefix={<Icon name="search" />}
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={this.onSearchTermChangeCallback}
|
||||
/>*/}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, Icon, ValuePicker } from '@grafana/ui';
|
||||
import { Button, HorizontalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -9,15 +9,9 @@ import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
|||
import Text from 'components/Text/Text';
|
||||
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
|
||||
import Rotation from 'containers/Rotation/Rotation';
|
||||
import { RotationCreateData } from 'containers/RotationForm/RotationForm.types';
|
||||
import ScheduleOverrideForm from 'containers/RotationForm/ScheduleOverrideForm';
|
||||
import {
|
||||
getFromString,
|
||||
getOverrideColor,
|
||||
getOverridesFromStore,
|
||||
getShiftsFromStore,
|
||||
} from 'models/schedule/schedule.helpers';
|
||||
import { Event, Schedule, Shift, ShiftEvents } from 'models/schedule/schedule.types';
|
||||
import { getOverrideColor, getOverridesFromStore } from 'models/schedule/schedule.helpers';
|
||||
import { Schedule, Shift, ShiftEvents } from 'models/schedule/schedule.types';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
|
@ -125,9 +119,6 @@ class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverri
|
|||
)}
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
{/* <div className={cx('add-rotations-layer')} onClick={this.handleAddOverride}>
|
||||
+ Add override
|
||||
</div>*/}
|
||||
</div>
|
||||
{shiftIdToShowRotationForm && (
|
||||
<ScheduleOverrideForm
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { ReactElement } from 'react';
|
||||
|
||||
import { FormItem, FormItemType } from 'components/GForm/GForm.types';
|
||||
import { PRIVATE_CHANNEL_NAME } from 'models/slack_channel/slack_channel.config';
|
||||
import { DEFAULT_USER_ROLES } from 'models/user/user.config';
|
||||
|
||||
const commonFields: FormItem[] = [
|
||||
{
|
||||
|
|
@ -92,12 +89,6 @@ const commonFields: FormItem[] = [
|
|||
},
|
||||
description: 'Specify how to notify a team member when their shift is the next one scheduled',
|
||||
},
|
||||
// {
|
||||
// name: 'send_empty_shifts_report',
|
||||
// normalize: (value) => Boolean(value),
|
||||
// label: 'Send reports about empty shifts to Slack',
|
||||
// type: FormItemType.Switch,
|
||||
// },
|
||||
];
|
||||
|
||||
export const iCalForm: { name: string; fields: FormItem[] } = {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue