mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 21:19:13 +00:00
Add option to enable/disable feed failure notices. (#716)
* Add new notifyOnFailure option * Disable state buttons when updating is set * Linting
This commit is contained in:
parent
c6a04c2ebd
commit
a9537c7961
1
changelog.d/716.feature
Normal file
1
changelog.d/716.feature
Normal file
@ -0,0 +1 @@
|
||||
Notifications for RSS feed failures can now be toggled on and off. The feature is now **off** by default.
|
@ -25,6 +25,7 @@ export interface FeedConnectionState extends IConnectionState {
|
||||
url: string;
|
||||
label?: string;
|
||||
template?: string;
|
||||
notifyOnFailure?: boolean;
|
||||
}
|
||||
|
||||
export interface FeedConnectionSecrets {
|
||||
@ -225,6 +226,10 @@ export class FeedConnection extends BaseConnection implements IConnection {
|
||||
// To avoid short term failures bubbling up, if the error is serious, we still bubble.
|
||||
return;
|
||||
}
|
||||
if (!this.state.notifyOnFailure) {
|
||||
// User hasn't opted into notifications on failure
|
||||
return;
|
||||
}
|
||||
if (!this.hasError) {
|
||||
await this.intent.sendEvent(this.roomId, {
|
||||
msgtype: 'm.notice',
|
||||
|
@ -4,7 +4,7 @@ import emoji from "node-emoji";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import { JiraIssue } from './Jira/Types';
|
||||
import { formatLabels, getPartialBodyForJiraIssue, hashId, getPartialBodyForGithubIssue, getPartialBodyForGithubRepo, MinimalGitHubRepo, MinimalGitHubIssue } from "./libRs";
|
||||
import { formatLabels, getPartialBodyForJiraIssue, hashId, getPartialBodyForGithubIssue, getPartialBodyForGithubRepo, MinimalGitHubIssue } from "./libRs";
|
||||
|
||||
interface IMinimalPR {
|
||||
html_url: string;
|
||||
|
@ -29,8 +29,5 @@ export class GitLabWatcher extends EventEmitter implements NotificationWatcherTa
|
||||
|
||||
private async getNotifications() {
|
||||
log.info(`Fetching events from GitLab for ${this.userId}`);
|
||||
const events = await this.client.getEvents({
|
||||
after: new Date(this.since)
|
||||
});
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import { Logger } from "matrix-appservice-bridge";
|
||||
import axios from "axios";
|
||||
|
||||
import { FeedConnection, FeedConnectionState, GitHubRepoConnection, GitHubRepoConnectionState } from "../Connections";
|
||||
import { AllowedEvents as GitHubAllowedEvents, AllowedEventsNames as GitHubAllowedEventsNames } from "../Connections/GithubRepo";
|
||||
import { AllowedEventsNames as GitHubAllowedEventsNames } from "../Connections/GithubRepo";
|
||||
|
||||
const log = new Logger("GoNebMigrator");
|
||||
|
||||
|
@ -14,7 +14,7 @@ class MockConnectionManager extends EventEmitter {
|
||||
super();
|
||||
}
|
||||
|
||||
getAllConnectionsOfType(type: unknown) {
|
||||
getAllConnectionsOfType() {
|
||||
return this.connections;
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,7 @@ class MockMessageQueue extends EventEmitter implements MessageQueue {
|
||||
this.emit('pushed', data, single);
|
||||
}
|
||||
|
||||
async pushWait<T, X>(data: MessageQueueMessage<T>, timeout?: number, single?: boolean): Promise<X> {
|
||||
async pushWait<X>(): Promise<X> {
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
||||
}
|
||||
@ -73,13 +73,13 @@ describe("FeedReader", () => {
|
||||
config, cm, mq,
|
||||
{
|
||||
getAccountData: <T>() => Promise.resolve({ 'http://test/': [] } as unknown as T),
|
||||
setAccountData: <T>() => Promise.resolve(),
|
||||
setAccountData: () => Promise.resolve(),
|
||||
},
|
||||
new MockHttpClient({ headers: {}, data: feedContents } as AxiosResponse) as unknown as AxiosStatic,
|
||||
);
|
||||
|
||||
const event: any = await new Promise((resolve) => {
|
||||
mq.on('pushed', (data, _) => { resolve(data); feedReader.stop() });
|
||||
mq.on('pushed', (data) => { resolve(data); feedReader.stop() });
|
||||
});
|
||||
|
||||
expect(event.eventName).to.equal('feed.entry');
|
||||
|
@ -22,4 +22,8 @@
|
||||
.remove {
|
||||
color: #FF5B55;
|
||||
background-color: transparent;
|
||||
|
||||
&:disabled {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
@ -28,10 +28,11 @@ const FeedRecentResults: FunctionComponent<{item: FeedResponseItem}> = ({ item }
|
||||
}
|
||||
const DOCUMENTATION_LINK = "https://matrix-org.github.io/matrix-hookshot/latest/setup/feeds.html#feed-templates";
|
||||
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ServiceConfig, FeedResponseItem, FeedConnectionState>> = ({existingConnection, onSave, onRemove, isMigrationCandidate}) => {
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ServiceConfig, FeedResponseItem, FeedConnectionState>> = ({existingConnection, onSave, onRemove, isMigrationCandidate, isUpdating}) => {
|
||||
const urlRef = createRef<HTMLInputElement>();
|
||||
const labelRef = createRef<HTMLInputElement>();
|
||||
const templateRef = createRef<HTMLInputElement>();
|
||||
const notifyRef = createRef<HTMLInputElement>();
|
||||
const canSave = !existingConnection?.id || (existingConnection?.canEdit ?? false);
|
||||
const canEdit = canSave && !isMigrationCandidate;
|
||||
const handleSave = useCallback((evt: Event) => {
|
||||
@ -45,10 +46,13 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<Se
|
||||
url,
|
||||
label: labelRef?.current?.value || existingConnection?.config.label,
|
||||
template: templateRef.current?.value || existingConnection?.config.template,
|
||||
});
|
||||
notifyOnFailure: notifyRef.current?.checked || existingConnection?.config.notifyOnFailure,
|
||||
})
|
||||
}
|
||||
}, [canSave, onSave, urlRef, labelRef, templateRef, existingConnection]);
|
||||
|
||||
}, [canSave, onSave, urlRef, labelRef, templateRef, notifyRef, existingConnection]);
|
||||
|
||||
const onlyVisibleOnExistingConnection = !!existingConnection;
|
||||
|
||||
return <form onSubmit={handleSave}>
|
||||
{ existingConnection && <FeedRecentResults item={existingConnection} />}
|
||||
|
||||
@ -58,14 +62,16 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<Se
|
||||
<InputField visible={true} label="Label" noPadding={true}>
|
||||
<input ref={labelRef} disabled={!canSave} type="text" value={existingConnection?.config.label} />
|
||||
</InputField>
|
||||
<InputField visible={true} label="Template" noPadding={true}>
|
||||
<InputField visible={onlyVisibleOnExistingConnection} label="Send a notice on read failure" noPadding={true}>
|
||||
<input ref={notifyRef} disabled={!canSave} type="checkbox" checked={existingConnection?.config.notifyOnFailure} />
|
||||
</InputField>
|
||||
<InputField visible={onlyVisibleOnExistingConnection} label="Template" noPadding={true}>
|
||||
<input ref={templateRef} disabled={!canSave} type="text" value={existingConnection?.config.template} placeholder={DEFAULT_TEMPLATE} />
|
||||
<p> See the <a target="_blank" rel="noopener noreferrer" href={DOCUMENTATION_LINK}>documentation</a> for help writing templates. </p>
|
||||
</InputField>
|
||||
|
||||
<ButtonSet>
|
||||
{ canSave && <Button type="submit">{ existingConnection?.id ? "Save" : "Subscribe" }</Button>}
|
||||
{ canEdit && existingConnection?.id && <Button intent="remove" onClick={onRemove}>Unsubscribe</Button>}
|
||||
{ canSave && <Button type="submit" disabled={isUpdating}>{ existingConnection?.id ? "Save" : "Subscribe" }</Button>}
|
||||
{ canEdit && existingConnection?.id && <Button intent="remove" onClick={onRemove} disabled={isUpdating}>Unsubscribe</Button>}
|
||||
</ButtonSet>
|
||||
|
||||
</form>;
|
||||
|
@ -28,7 +28,7 @@ const EXAMPLE_SCRIPT = `if (data.counter === undefined) {
|
||||
const DOCUMENTATION_LINK = "https://matrix-org.github.io/matrix-hookshot/latest/setup/webhooks.html#script-api";
|
||||
const CODE_MIRROR_EXTENSIONS = [javascript({})];
|
||||
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ServiceConfig, GenericHookResponseItem, GenericHookConnectionState>> = ({serviceConfig, existingConnection, onSave, onRemove}) => {
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ServiceConfig, GenericHookResponseItem, GenericHookConnectionState>> = ({serviceConfig, existingConnection, onSave, onRemove, isUpdating}) => {
|
||||
const [transFn, setTransFn] = useState<string>(existingConnection?.config.transformationFunction as string || EXAMPLE_SCRIPT);
|
||||
const [transFnEnabled, setTransFnEnabled] = useState(serviceConfig.allowJsTransformationFunctions && !!existingConnection?.config.transformationFunction);
|
||||
const nameRef = createRef<HTMLInputElement>();
|
||||
@ -67,8 +67,8 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<Se
|
||||
<p> See the <a target="_blank" rel="noopener noreferrer" href={DOCUMENTATION_LINK}>documentation</a> for help writing transformation functions </p>
|
||||
</InputField>
|
||||
<ButtonSet>
|
||||
{ canEdit && <Button type="submit">{ existingConnection ? "Save" : "Add webhook" }</Button>}
|
||||
{ canEdit && existingConnection && <Button intent="remove" onClick={onRemove}>Remove webhook</Button>}
|
||||
{ canEdit && <Button disabled={isUpdating} type="submit">{ existingConnection ? "Save" : "Add webhook" }</Button>}
|
||||
{ canEdit && existingConnection && <Button disabled={isUpdating} intent="remove" onClick={onRemove}>Remove webhook</Button>}
|
||||
</ButtonSet>
|
||||
</form>;
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ function getRepoFullName(state: GitHubRepoConnectionState) {
|
||||
}
|
||||
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitHubRepoResponseItem, GitHubRepoConnectionState>> = ({
|
||||
showAuthPrompt, loginLabel, serviceConfig, api, existingConnection, onSave, onRemove
|
||||
showAuthPrompt, loginLabel, serviceConfig, api, existingConnection, onSave, onRemove, isUpdating
|
||||
}) => {
|
||||
// Assume true if we have no auth prompt.
|
||||
const [authedResponse, setAuthResponse] = useState<GetAuthResponse|null>(null);
|
||||
@ -159,8 +159,8 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
||||
</ul>
|
||||
</InputField>
|
||||
<ButtonSet>
|
||||
{ canEdit && consideredAuthenticated && <Button type="submit" disabled={!existingConnection && !connectionState}>{ existingConnection?.id ? "Save" : "Add repository" }</Button>}
|
||||
{ canEdit && existingConnection?.id && <Button intent="remove" onClick={onRemove}>Remove repository</Button>}
|
||||
{ canEdit && consideredAuthenticated && <Button type="submit" disabled={isUpdating || !existingConnection && !connectionState}>{ existingConnection?.id ? "Save" : "Add repository" }</Button>}
|
||||
{ canEdit && existingConnection?.id && <Button disabled={isUpdating} intent="remove" onClick={onRemove}>Remove repository</Button>}
|
||||
</ButtonSet>
|
||||
</form>;
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ import { DropItem } from "../elements/DropdownSearch";
|
||||
import { ConnectionSearch } from "../elements/ConnectionSearch";
|
||||
|
||||
const EventType = "uk.half-shot.matrix-hookshot.gitlab.repository";
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitLabRepoResponseItem, GitLabRepoConnectionState>> = ({api, existingConnection, onSave, onRemove }) => {
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitLabRepoResponseItem, GitLabRepoConnectionState>> = ({api, existingConnection, onSave, onRemove, isUpdating }) => {
|
||||
const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
|
||||
|
||||
const toggleEnabledHook = useCallback((evt: any) => {
|
||||
@ -107,8 +107,8 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
||||
</ul>
|
||||
</InputField>
|
||||
<ButtonSet>
|
||||
{ canEdit && <Button type="submit" disabled={!existingConnection && !newConnectionState}>{ existingConnection ? "Save" : "Add project" }</Button>}
|
||||
{ canEdit && existingConnection && <Button intent="remove" onClick={onRemove}>Remove project</Button>}
|
||||
{ canEdit && <Button type="submit" disabled={isUpdating || !existingConnection && !newConnectionState}>{ existingConnection ? "Save" : "Add project" }</Button>}
|
||||
{ canEdit && existingConnection && <Button disabled={isUpdating} intent="remove" onClick={onRemove}>Remove project</Button>}
|
||||
</ButtonSet>
|
||||
</form>;
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ import { DropItem } from "../elements/DropdownSearch";
|
||||
|
||||
const EventType = "uk.half-shot.matrix-hookshot.jira.project";
|
||||
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, JiraProjectResponseItem, JiraProjectConnectionState>> = ({api, existingConnection, onSave, onRemove }) => {
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, JiraProjectResponseItem, JiraProjectConnectionState>> = ({api, existingConnection, onSave, onRemove, isUpdating }) => {
|
||||
const [allowedEvents, setAllowedEvents] = useState<string[]>(existingConnection?.config.events || ['issue_created']);
|
||||
|
||||
const toggleEvent = useCallback((evt: Event) => {
|
||||
@ -93,8 +93,8 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
||||
</ul>
|
||||
</InputField>
|
||||
<ButtonSet>
|
||||
{ canEdit && <Button type="submit" disabled={!existingConnection && !newConnectionState}>{ existingConnection ? "Save" : "Add project" }</Button>}
|
||||
{ canEdit && existingConnection && <Button intent="remove" onClick={onRemove}>Remove project</Button>}
|
||||
{ canEdit && <Button type="submit" disabled={isUpdating || !existingConnection && !newConnectionState}>{ existingConnection ? "Save" : "Add project" }</Button>}
|
||||
{ canEdit && existingConnection && <Button disabled={isUpdating} intent="remove" onClick={onRemove}>Remove project</Button>}
|
||||
</ButtonSet>
|
||||
</form>;
|
||||
};
|
||||
|
@ -13,6 +13,8 @@ export interface ConnectionConfigurationProps<SConfig, ConnectionType extends Ge
|
||||
loginLabel?: string;
|
||||
showAuthPrompt?: boolean;
|
||||
onSave: (newConfig: ConnectionState) => void,
|
||||
isUpdating: boolean,
|
||||
isMigrationCandidate?: boolean,
|
||||
existingConnection?: ConnectionType;
|
||||
onRemove?: () => void,
|
||||
api: BridgeAPI;
|
||||
@ -64,6 +66,7 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
const [ canEditRoom, setCanEditRoom ] = useState<boolean>(false);
|
||||
// We need to increment this every time we create a connection in order to properly reset the state.
|
||||
const [ newConnectionKey, incrementConnectionKey ] = useReducer<number, undefined>(n => n+1, 0);
|
||||
const [ updatingConnection, isUpdatingConnection ] = useState<boolean>(false);
|
||||
|
||||
const clearCurrentError = () => {
|
||||
setError(error => error?.forPrevious ? error : null);
|
||||
@ -125,6 +128,7 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
}, [api, type]);
|
||||
|
||||
const handleSaveOnCreation = useCallback((config: ConnectionState) => {
|
||||
isUpdatingConnection(true);
|
||||
api.createConnection(roomId, connectionEventType, config).then(result => {
|
||||
// Force reload
|
||||
incrementConnectionKey(undefined);
|
||||
@ -140,6 +144,8 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
header: "Failed to create connection",
|
||||
message: ex instanceof BridgeAPIError ? ex.message : "Unknown error"
|
||||
});
|
||||
}).finally(() => {
|
||||
isUpdatingConnection(false);
|
||||
});
|
||||
}, [api, roomId, connectionEventType]);
|
||||
|
||||
@ -167,6 +173,7 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
onSave={handleSaveOnCreation}
|
||||
loginLabel={text.login}
|
||||
showAuthPrompt={showAuthPrompt}
|
||||
isUpdating={updatingConnection}
|
||||
/>}
|
||||
</section>}
|
||||
{ !error && connections === null && <LoadingSpinner /> }
|
||||
@ -178,6 +185,7 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
serviceConfig={serviceConfig}
|
||||
existingConnection={c}
|
||||
onSave={(config) => {
|
||||
isUpdatingConnection(true);
|
||||
api.updateConnection(roomId, c.id, config).then(() => {
|
||||
c.config = config;
|
||||
// Force reload
|
||||
@ -189,6 +197,8 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
header: "Failed to create connection",
|
||||
message: ex instanceof BridgeAPIError ? ex.message : "Unknown error"
|
||||
});
|
||||
}).finally(() => {
|
||||
isUpdatingConnection(false);
|
||||
});
|
||||
}}
|
||||
onRemove={() => {
|
||||
@ -203,6 +213,7 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
});
|
||||
});
|
||||
}}
|
||||
isUpdating={updatingConnection}
|
||||
/>
|
||||
</ListItem>)
|
||||
}
|
||||
@ -214,6 +225,8 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
|
||||
api={api}
|
||||
serviceConfig={serviceConfig}
|
||||
existingConnection={c}
|
||||
isUpdating={updatingConnection}
|
||||
isMigrationCandidate={true}
|
||||
onSave={handleSaveOnCreation}
|
||||
/>
|
||||
</ListItem>) }
|
||||
|
Loading…
x
Reference in New Issue
Block a user