make links clickable in resolution notes (#4572)
# What this PR does make links clickable in resolution notes  ## Which issue(s) this PR closes Closes https://github.com/grafana/oncall/issues/4231 Closes https://github.com/grafana/oncall/issues/505 <!-- *Note*: if you have more than one GitHub issue that this PR closes, be sure to preface each issue link with a [closing keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue). This ensures that the issue(s) are auto-closed once the PR has been merged. --> ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
This commit is contained in:
parent
48b7eca26d
commit
c39dd8b4cd
6 changed files with 42 additions and 48 deletions
|
|
@ -153,6 +153,8 @@
|
|||
"dayjs": "^1.11.5",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"linkify-react": "^4.1.3",
|
||||
"linkifyjs": "^4.1.3",
|
||||
"mobx": "6.12.0",
|
||||
"mobx-react": "9.1.0",
|
||||
"object-hash": "^3.0.0",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import {
|
|||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
import { observer } from 'mobx-react';
|
||||
import { parseUrl } from 'query-string';
|
||||
import { Controller, useForm, useFormContext, FormProvider } from 'react-hook-form';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
|
|
@ -40,6 +39,7 @@ import { useStore } from 'state/useStore';
|
|||
import { UserActions } from 'utils/authorization/authorization';
|
||||
import { PLUGIN_ROOT, generateAssignToTeamInputDescription, DOCS_ROOT, INTEGRATION_SERVICENOW } from 'utils/consts';
|
||||
import { useIsLoading } from 'utils/hooks';
|
||||
import { validateURL } from 'utils/string';
|
||||
import { OmitReadonlyMembers } from 'utils/types';
|
||||
|
||||
import { prepareForEdit } from './IntegrationForm.helpers';
|
||||
|
|
@ -406,10 +406,6 @@ export const IntegrationForm = observer(
|
|||
);
|
||||
}
|
||||
|
||||
function validateURL(urlFieldValue: string): string | boolean {
|
||||
return !parseUrl(urlFieldValue) ? 'Instance URL is invalid' : true;
|
||||
}
|
||||
|
||||
async function onFormSubmit(formData: IntegrationFormFields): Promise<void> {
|
||||
const labels = labelsRef.current?.getValue();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { css } from '@emotion/css';
|
|||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Drawer, Field, HorizontalGroup, Input, useStyles2, Button } from '@grafana/ui';
|
||||
import { observer } from 'mobx-react';
|
||||
import { parseUrl } from 'query-string';
|
||||
import { Controller, FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { ActionKey } from 'models/loader/action-keys';
|
||||
|
|
@ -12,6 +11,7 @@ import { ApiSchemas } from 'network/oncall-api/api.types';
|
|||
import { useCurrentIntegration } from 'pages/integration/OutgoingTab/OutgoingTab.hooks';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { useIsLoading } from 'utils/hooks';
|
||||
import { validateURL } from 'utils/string';
|
||||
import { OmitReadonlyMembers } from 'utils/types';
|
||||
import { openNotification } from 'utils/utils';
|
||||
|
||||
|
|
@ -130,10 +130,6 @@ export const ServiceNowConfigDrawer: React.FC<ServiceNowConfigurationDrawerProps
|
|||
</>
|
||||
);
|
||||
|
||||
function validateURL(urlFieldValue: string): string | boolean {
|
||||
return !parseUrl(urlFieldValue) ? 'Instance URL is invalid' : true;
|
||||
}
|
||||
|
||||
async function onFormSubmit(formData: FormFields): Promise<void> {
|
||||
const data: OmitReadonlyMembers<ApiSchemas['AlertReceiveChannel']> = {
|
||||
...currentIntegration,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
withTheme2,
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
import Linkify from 'linkify-react';
|
||||
import { observer } from 'mobx-react';
|
||||
import moment from 'moment-timezone';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
|
|
@ -517,7 +518,6 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
|
|||
renderTimeline = () => {
|
||||
const {
|
||||
store,
|
||||
history,
|
||||
match: {
|
||||
params: { id },
|
||||
},
|
||||
|
|
@ -569,7 +569,17 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
|
|||
</Text>
|
||||
)}
|
||||
<Text type="primary">
|
||||
{reactStringReplace(item.action, /\{\{([^}]+)\}\}/g, this.getPlaceholderReplaceFn(item, history))}
|
||||
<Linkify
|
||||
options={{
|
||||
render: ({ attributes, content }) => (
|
||||
<a {...attributes} rel="noreferrer noopener" target="_blank">
|
||||
<Text underline>{content}</Text>
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{this.replaceTextInResolutionNote(item)}
|
||||
</Linkify>
|
||||
</Text>
|
||||
<Text type="secondary" size="small">
|
||||
{moment(item.created_at).format('MMM DD, YYYY HH:mm:ss Z')}
|
||||
|
|
@ -636,17 +646,14 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
|
|||
await this.update();
|
||||
};
|
||||
|
||||
getPlaceholderReplaceFn = (entity: any, history) => {
|
||||
getPlaceholderReplaceFn = (entity: TimeLineItem) => {
|
||||
return (match: string) => {
|
||||
switch (match) {
|
||||
case 'author':
|
||||
return (
|
||||
<span
|
||||
onClick={() => history.push(`${PLUGIN_ROOT}/users/${entity?.author?.pk}`)}
|
||||
style={{ textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
{entity.author?.username}
|
||||
</span>
|
||||
<a href={`${PLUGIN_ROOT}/users/${entity?.author?.pk}`} target="_blank" rel="noopener noreferrer">
|
||||
<Text underline>{entity.author?.username}</Text>
|
||||
</a>
|
||||
);
|
||||
default:
|
||||
return '{{' + match + '}}';
|
||||
|
|
@ -654,6 +661,9 @@ class _IncidentPage extends React.Component<IncidentPageProps, IncidentPageState
|
|||
};
|
||||
};
|
||||
|
||||
replaceTextInResolutionNote = (item: TimeLineItem) =>
|
||||
reactStringReplace(item.action, /\{\{([^}]+)\}\}/g, this.getPlaceholderReplaceFn(item));
|
||||
|
||||
getOnActionButtonClick = (incidentId: ApiSchemas['AlertGroup']['pk'], action: AlertAction) => {
|
||||
const { store } = this.props;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { parseURL } from './url';
|
||||
|
||||
// Truncate a string to a given maximum length, adding ellipsis if it was truncated.
|
||||
export function truncateTitle(title: string, length: number): string {
|
||||
if (title.length <= length) {
|
||||
|
|
@ -24,4 +26,6 @@ export const safeJSONStringify = (value: unknown) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const VALID_URL_PATTERN = /(http|https)\:\/\/.+?\..+/;
|
||||
export function validateURL(urlFieldValue: string): string | boolean {
|
||||
return !parseURL(urlFieldValue) ? 'URL is invalid' : true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9582,6 +9582,16 @@ lines-and-columns@^1.1.6:
|
|||
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
|
||||
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
|
||||
|
||||
linkify-react@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/linkify-react/-/linkify-react-4.1.3.tgz#461d348b4bdab3fcd0452ae1b5bbc22536395b97"
|
||||
integrity sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==
|
||||
|
||||
linkifyjs@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.3.tgz#0edbc346428a7390a23ea2e5939f76112c9ae07f"
|
||||
integrity sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg==
|
||||
|
||||
lint-staged@^10.2.11:
|
||||
version "10.5.4"
|
||||
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665"
|
||||
|
|
@ -13459,16 +13469,7 @@ string-template@~0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
|
||||
integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
|
|
@ -13586,7 +13587,7 @@ stringify-object@^3.3.0:
|
|||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
|
@ -13607,13 +13608,6 @@ strip-ansi@^5.2.0:
|
|||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
|
|
@ -14954,7 +14948,8 @@ wordwrap@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
name wrap-ansi-cjs
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
|
@ -14972,15 +14967,6 @@ wrap-ansi@^6.2.0:
|
|||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue