Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to enable/disable feed failure notices. #716

Merged
merged 4 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/716.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Notifications for RSS feed failures can now be toggled on and off. The feature is now **off** by default.
5 changes: 5 additions & 0 deletions src/Connections/FeedConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface FeedConnectionState extends IConnectionState {
url: string;
label?: string;
template?: string;
notifyOnFailure?: boolean;
}

export interface FeedConnectionSecrets {
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion src/FormatUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 0 additions & 3 deletions src/Notifications/GitLabWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
AndrewFerr marked this conversation as resolved.
Show resolved Hide resolved
after: new Date(this.since)
});
}
}
2 changes: 1 addition & 1 deletion src/Widgets/GoNebMigrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
8 changes: 4 additions & 4 deletions tests/FeedReader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MockConnectionManager extends EventEmitter {
super();
}

getAllConnectionsOfType(type: unknown) {
getAllConnectionsOfType() {
return this.connections;
}
}
Expand All @@ -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');
}
}
Expand Down Expand Up @@ -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');
Expand Down
4 changes: 4 additions & 0 deletions web/components/elements/Button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@
.remove {
color: #FF5B55;
background-color: transparent;

&:disabled {
background-color: transparent;
}
}
22 changes: 14 additions & 8 deletions web/components/roomConfig/FeedsConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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} />}

Expand All @@ -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}>
AndrewFerr marked this conversation as resolved.
Show resolved Hide resolved
<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>;
Expand Down
6 changes: 3 additions & 3 deletions web/components/roomConfig/GenericWebhookConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
Expand Down Expand Up @@ -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>;
};
Expand Down
6 changes: 3 additions & 3 deletions web/components/roomConfig/GithubRepoConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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>;
};
Expand Down
6 changes: 3 additions & 3 deletions web/components/roomConfig/GitlabRepoConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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>;
};
Expand Down
6 changes: 3 additions & 3 deletions web/components/roomConfig/JiraProjectConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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>;
};
Expand Down
13 changes: 13 additions & 0 deletions web/components/roomConfig/RoomConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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]);

Expand Down Expand Up @@ -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 /> }
Expand All @@ -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
Expand All @@ -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={() => {
Expand All @@ -203,6 +213,7 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
});
});
}}
isUpdating={updatingConnection}
/>
</ListItem>)
}
Expand All @@ -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>) }
Expand Down