mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 21:19:13 +00:00
Drop ignoreHooks
configuration. (#592)
* Refactor HookFilter to only support enabledEvents (and add a function to convert) * Convert connections to deprecate ignoreHooks * Update documentation * Split out EventHookCheckbox * Refactor frontend to support enableHooks only mode * drop old field name * changelog * Fix enabledHooks for widgets * Fixes across the board * Update test description * Cleanup * Fix HookFilter * Fixup checkboxes * Cleanup
This commit is contained in:
parent
4048cc8b01
commit
1e8a112a28
2
changelog.d/592.feature
Normal file
2
changelog.d/592.feature
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
The GitHub/GitLab connection state configuration has changed. The configuration option `ignoreHooks` is now deprecated, and new connections may not use this options.
|
||||||
|
Users should instead explicitly configure all the hooks they want to enable with the `enableHooks` option. Existing connections will continue to work with both options.
|
@ -27,8 +27,8 @@ This connection supports a few options which can be defined in the room state:
|
|||||||
|
|
||||||
| Option | Description | Allowed values | Default |
|
| Option | Description | Allowed values | Default |
|
||||||
|--------|-------------|----------------|---------|
|
|--------|-------------|----------------|---------|
|
||||||
|enableHooks [^1]|Enable notifications for some event types|Array of: [Supported event types](#supported-event-types) |*empty*|
|
|enableHooks [^1]|Enable notifications for some event types|Array of: [Supported event types](#supported-event-types) |If not defined, defaults are mentioned below|
|
||||||
|ignoreHooks [^1]|Choose to exclude notifications for some event types|Array of: [Supported event types](#supported-event-types) |*empty*|
|
|ignoreHooks [^1]|**deprecated** Choose to exclude notifications for some event types|Array of: [Supported event types](#supported-event-types) |*empty*|
|
||||||
|commandPrefix|Choose the prefix to use when sending commands to the bot|A string, ideally starts with "!"|`!gh`|
|
|commandPrefix|Choose the prefix to use when sending commands to the bot|A string, ideally starts with "!"|`!gh`|
|
||||||
|showIssueRoomLink|When new issues are created, provide a Matrix alias link to the issue room|`true/false`|`false`|
|
|showIssueRoomLink|When new issues are created, provide a Matrix alias link to the issue room|`true/false`|`false`|
|
||||||
|prDiff|Show a diff in the room when a PR is created, subject to limits|`{enabled: boolean, maxLines: number}`|`{enabled: false}`|
|
|prDiff|Show a diff in the room when a PR is created, subject to limits|`{enabled: boolean, maxLines: number}`|`{enabled: false}`|
|
||||||
@ -43,14 +43,16 @@ This connection supports a few options which can be defined in the room state:
|
|||||||
|workflowRun.excludingWorkflows|Never report workflow runs with a matching workflow name.|Array of: String matching a workflow name|*empty*|
|
|workflowRun.excludingWorkflows|Never report workflow runs with a matching workflow name.|Array of: String matching a workflow name|*empty*|
|
||||||
|
|
||||||
|
|
||||||
[^1]: `ignoreHooks` takes precedence over `enableHooks`.
|
[^1]: `ignoreHooks` is no longer accepted for new state events. Use `enableHooks` to explicitly state all events you want to see.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Supported event types
|
### Supported event types
|
||||||
|
|
||||||
This connection supports sending messages when the following actions happen on the repository.
|
This connection supports sending messages when the following actions happen on the repository.
|
||||||
|
|
||||||
Note: Some of these event types are enabled by default (marked with a `*`)
|
Note: Some of these event types are enabled by default (marked with a `*`). When `ignoreHooks` *is* defined,
|
||||||
|
the events marked as default below will be enabled. Otherwise, this is ignored.
|
||||||
|
|
||||||
- issue *
|
- issue *
|
||||||
- issue.created *
|
- issue.created *
|
||||||
|
@ -23,26 +23,33 @@ This connection supports a few options which can be defined in the room state:
|
|||||||
|
|
||||||
| Option | Description | Allowed values | Default |
|
| Option | Description | Allowed values | Default |
|
||||||
|--------|-------------|----------------|---------|
|
|--------|-------------|----------------|---------|
|
||||||
|ignoreHooks|Choose to exclude notifications for some event types|Array of: [Supported event types](#supported-event-types) |*empty*|
|
|
||||||
|commandPrefix|Choose the prefix to use when sending commands to the bot|A string, ideally starts with "!"|`!gh`|
|
|commandPrefix|Choose the prefix to use when sending commands to the bot|A string, ideally starts with "!"|`!gh`|
|
||||||
|pushTagsRegex|Only mention pushed tags which match this regex|Regex string|*empty*|
|
|enableHooks [^1]|Enable notifications for some event types|Array of: [Supported event types](#supported-event-types) |If not defined, defaults are mentioned below|
|
||||||
|includingLabels|Only notify on issues matching these label names|Array of: String matching a label name|*empty*|
|
|
||||||
|excludingLabels|Never notify on issues matching these label names|Array of: String matching a label name|*empty*|
|
|excludingLabels|Never notify on issues matching these label names|Array of: String matching a label name|*empty*|
|
||||||
|
|ignoreHooks [^1]|**deprecated** Choose to exclude notifications for some event types|Array of: [Supported event types](#supported-event-types) |*empty*|
|
||||||
|includeCommentBody|Include the body of a comment when notifying on merge requests|Boolean|false|
|
|includeCommentBody|Include the body of a comment when notifying on merge requests|Boolean|false|
|
||||||
|
|includingLabels|Only notify on issues matching these label names|Array of: String matching a label name|*empty*|
|
||||||
|
|pushTagsRegex|Only mention pushed tags which match this regex|Regex string|*empty*|
|
||||||
|
|
||||||
|
|
||||||
|
[^1]: `ignoreHooks` is no longer accepted for new state events. Use `enableHooks` to explicitly state all events you want to see.
|
||||||
|
|
||||||
|
|
||||||
### Supported event types
|
### Supported event types
|
||||||
|
|
||||||
This connection supports sending messages when the following actions happen on the repository.
|
This connection supports sending messages when the following actions happen on the repository.
|
||||||
|
|
||||||
- merge_request
|
Note: Some of these event types are enabled by default (marked with a `*`). When `ignoreHooks` *is* defined,
|
||||||
- merge_request.close
|
the events marked as default below will be enabled. Otherwise, this is ignored.
|
||||||
- merge_request.merge
|
|
||||||
- merge_request.open
|
- merge_request *
|
||||||
- merge_request.review.comments
|
- merge_request.close *
|
||||||
- merge_request.review
|
- merge_request.merge *
|
||||||
- push
|
- merge_request.open *
|
||||||
- release
|
- merge_request.review.comments *
|
||||||
- release.created
|
- merge_request.review *
|
||||||
- tag_push
|
- push *
|
||||||
- wiki
|
- release *
|
||||||
|
- release.created *
|
||||||
|
- tag_push *
|
||||||
|
- wiki *
|
||||||
|
@ -10,14 +10,14 @@ const log = new Logger("CommandConnection");
|
|||||||
* Connection class that handles commands for a given connection. Should be used
|
* Connection class that handles commands for a given connection. Should be used
|
||||||
* by connections expecting to handle user input.
|
* by connections expecting to handle user input.
|
||||||
*/
|
*/
|
||||||
export abstract class CommandConnection<StateType extends IConnectionState = IConnectionState> extends BaseConnection {
|
export abstract class CommandConnection<StateType extends IConnectionState = IConnectionState, ValidatedStateType extends StateType = StateType> extends BaseConnection {
|
||||||
protected enabledHelpCategories?: string[];
|
protected enabledHelpCategories?: string[];
|
||||||
protected includeTitlesInHelp?: boolean;
|
protected includeTitlesInHelp?: boolean;
|
||||||
constructor(
|
constructor(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
stateKey: string,
|
stateKey: string,
|
||||||
canonicalStateType: string,
|
canonicalStateType: string,
|
||||||
protected state: StateType,
|
protected state: ValidatedStateType,
|
||||||
private readonly botClient: MatrixClient,
|
private readonly botClient: MatrixClient,
|
||||||
private readonly botCommands: BotCommands,
|
private readonly botCommands: BotCommands,
|
||||||
private readonly helpMessage: HelpFunction,
|
private readonly helpMessage: HelpFunction,
|
||||||
@ -39,7 +39,7 @@ export abstract class CommandConnection<StateType extends IConnectionState = ICo
|
|||||||
this.state = this.validateConnectionState(stateEv.content);
|
this.state = this.validateConnectionState(stateEv.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract validateConnectionState(content: unknown): StateType;
|
protected abstract validateConnectionState(content: unknown): ValidatedStateType;
|
||||||
|
|
||||||
public async onMessageEvent(ev: MatrixEvent<MatrixMessageContent>, checkPermission: PermissionCheckFn) {
|
public async onMessageEvent(ev: MatrixEvent<MatrixMessageContent>, checkPermission: PermissionCheckFn) {
|
||||||
const commandResult = await handleCommand(
|
const commandResult = await handleCommand(
|
||||||
|
@ -40,8 +40,12 @@ interface IQueryRoomOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GitHubRepoConnectionOptions extends IConnectionState {
|
export interface GitHubRepoConnectionOptions extends IConnectionState {
|
||||||
enableHooks?: AllowedEventsNames[],
|
/**
|
||||||
|
* Do not use. Use `enableHooks`.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
ignoreHooks?: AllowedEventsNames[],
|
ignoreHooks?: AllowedEventsNames[],
|
||||||
|
enableHooks?: AllowedEventsNames[],
|
||||||
showIssueRoomLink?: boolean;
|
showIssueRoomLink?: boolean;
|
||||||
prDiff?: {
|
prDiff?: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -61,11 +65,17 @@ export interface GitHubRepoConnectionOptions extends IConnectionState {
|
|||||||
excludingWorkflows?: string[];
|
excludingWorkflows?: string[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitHubRepoConnectionState extends GitHubRepoConnectionOptions {
|
export interface GitHubRepoConnectionState extends GitHubRepoConnectionOptions {
|
||||||
org: string;
|
org: string;
|
||||||
repo: string;
|
repo: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ConnectionValidatedState extends GitHubRepoConnectionState {
|
||||||
|
ignoreHooks: undefined,
|
||||||
|
enableHooks: AllowedEventsNames[],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface GitHubRepoConnectionOrgTarget {
|
export interface GitHubRepoConnectionOrgTarget {
|
||||||
name: string;
|
name: string;
|
||||||
@ -136,8 +146,17 @@ const AllowedEvents: AllowedEventsNames[] = [
|
|||||||
* These hooks are enabled by default, unless they are
|
* These hooks are enabled by default, unless they are
|
||||||
* specifed in the ignoreHooks option.
|
* specifed in the ignoreHooks option.
|
||||||
*/
|
*/
|
||||||
const AllowHookByDefault: AllowedEventsNames[] = [
|
const DefaultHooks: AllowedEventsNames[] = [
|
||||||
|
"issue.changed",
|
||||||
|
"issue.created",
|
||||||
|
"issue.edited",
|
||||||
|
"issue.labeled",
|
||||||
"issue",
|
"issue",
|
||||||
|
"pull_request.closed",
|
||||||
|
"pull_request.merged",
|
||||||
|
"pull_request.opened",
|
||||||
|
"pull_request.ready_for_review",
|
||||||
|
"pull_request.reviewed",
|
||||||
"pull_request",
|
"pull_request",
|
||||||
"release.created"
|
"release.created"
|
||||||
];
|
];
|
||||||
@ -151,6 +170,10 @@ const ConnectionStateSchema = {
|
|||||||
},
|
},
|
||||||
org: {type: "string"},
|
org: {type: "string"},
|
||||||
repo: {type: "string"},
|
repo: {type: "string"},
|
||||||
|
/**
|
||||||
|
* Legacy state.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
ignoreHooks: {
|
ignoreHooks: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
@ -310,19 +333,25 @@ export interface GitHubTargetFilter {
|
|||||||
* Handles rooms connected to a GitHub repo.
|
* Handles rooms connected to a GitHub repo.
|
||||||
*/
|
*/
|
||||||
@Connection
|
@Connection
|
||||||
export class GitHubRepoConnection extends CommandConnection<GitHubRepoConnectionState> implements IConnection {
|
export class GitHubRepoConnection extends CommandConnection<GitHubRepoConnectionState, ConnectionValidatedState> implements IConnection {
|
||||||
|
static validateState(state: unknown, isExistingState = false): ConnectionValidatedState {
|
||||||
static validateState(state: unknown, isExistingState = false): GitHubRepoConnectionState {
|
|
||||||
const validator = new Ajv({ allowUnionTypes: true }).compile(ConnectionStateSchema);
|
const validator = new Ajv({ allowUnionTypes: true }).compile(ConnectionStateSchema);
|
||||||
if (validator(state)) {
|
if (validator(state)) {
|
||||||
// Validate ignoreHooks IF this is an incoming update (we can be less strict for existing state)
|
|
||||||
if (!isExistingState && state.ignoreHooks && !state.ignoreHooks.every(h => AllowedEvents.includes(h))) {
|
|
||||||
throw new ApiError('`ignoreHooks` must only contain allowed values', ErrCode.BadValue);
|
|
||||||
}
|
|
||||||
if (!isExistingState && state.enableHooks && !state.enableHooks.every(h => AllowedEvents.includes(h))) {
|
if (!isExistingState && state.enableHooks && !state.enableHooks.every(h => AllowedEvents.includes(h))) {
|
||||||
throw new ApiError('`enableHooks` must only contain allowed values', ErrCode.BadValue);
|
throw new ApiError('`enableHooks` must only contain allowed values', ErrCode.BadValue);
|
||||||
}
|
}
|
||||||
return state;
|
if (state.ignoreHooks) {
|
||||||
|
if (!isExistingState) {
|
||||||
|
throw new ApiError('`ignoreHooks` cannot be used with new connections', ErrCode.BadValue);
|
||||||
|
}
|
||||||
|
log.warn(`Room has old state key 'ignoreHooks'. Converting to compatible enabledHooks filter`);
|
||||||
|
state.enableHooks = HookFilter.convertIgnoredHooksToEnabledHooks(state.enableHooks, state.ignoreHooks, DefaultHooks);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ignoreHooks: undefined,
|
||||||
|
enableHooks: state.enableHooks ?? [...DefaultHooks]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
throw new ValidatorApiError(validator.errors);
|
throw new ValidatorApiError(validator.errors);
|
||||||
}
|
}
|
||||||
@ -471,27 +500,25 @@ export class GitHubRepoConnection extends CommandConnection<GitHubRepoConnection
|
|||||||
|
|
||||||
constructor(roomId: string,
|
constructor(roomId: string,
|
||||||
private readonly as: Appservice,
|
private readonly as: Appservice,
|
||||||
state: GitHubRepoConnectionState,
|
state: ConnectionValidatedState,
|
||||||
private readonly tokenStore: UserTokenStore,
|
private readonly tokenStore: UserTokenStore,
|
||||||
stateKey: string,
|
stateKey: string,
|
||||||
private readonly githubInstance: GithubInstance,
|
private readonly githubInstance: GithubInstance,
|
||||||
private readonly config: BridgeConfigGitHub,
|
private readonly config: BridgeConfigGitHub,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
roomId,
|
roomId,
|
||||||
stateKey,
|
stateKey,
|
||||||
GitHubRepoConnection.CanonicalEventType,
|
GitHubRepoConnection.CanonicalEventType,
|
||||||
state,
|
state,
|
||||||
as.botClient,
|
as.botClient,
|
||||||
GitHubRepoConnection.botCommands,
|
GitHubRepoConnection.botCommands,
|
||||||
GitHubRepoConnection.helpMessage,
|
GitHubRepoConnection.helpMessage,
|
||||||
"!gh",
|
"!gh",
|
||||||
"github",
|
"github",
|
||||||
);
|
);
|
||||||
this.hookFilter = new HookFilter(
|
this.hookFilter = new HookFilter(
|
||||||
AllowHookByDefault,
|
|
||||||
state.enableHooks,
|
state.enableHooks,
|
||||||
state.ignoreHooks,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,8 +557,7 @@ export class GitHubRepoConnection extends CommandConnection<GitHubRepoConnection
|
|||||||
|
|
||||||
public async onStateUpdate(stateEv: MatrixEvent<unknown>) {
|
public async onStateUpdate(stateEv: MatrixEvent<unknown>) {
|
||||||
await super.onStateUpdate(stateEv);
|
await super.onStateUpdate(stateEv);
|
||||||
this.hookFilter.enabledHooks = this.state.enableHooks ?? [];
|
this.hookFilter.enabledHooks = this.state.enableHooks;
|
||||||
this.hookFilter.ignoredHooks = this.state.ignoreHooks ?? [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public isInterestedInStateEvent(eventType: string, stateKey: string) {
|
public isInterestedInStateEvent(eventType: string, stateKey: string) {
|
||||||
@ -1307,7 +1333,6 @@ export class GitHubRepoConnection extends CommandConnection<GitHubRepoConnection
|
|||||||
await this.as.botClient.sendStateEvent(this.roomId, GitHubRepoConnection.CanonicalEventType, this.stateKey, validatedConfig);
|
await this.as.botClient.sendStateEvent(this.roomId, GitHubRepoConnection.CanonicalEventType, this.stateKey, validatedConfig);
|
||||||
this.state = validatedConfig;
|
this.state = validatedConfig;
|
||||||
this.hookFilter.enabledHooks = this.state.enableHooks ?? [];
|
this.hookFilter.enabledHooks = this.state.enableHooks ?? [];
|
||||||
this.hookFilter.ignoredHooks = this.state.ignoreHooks ?? [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onRemove() {
|
public async onRemove() {
|
||||||
|
@ -21,6 +21,11 @@ import { HookFilter } from "../HookFilter";
|
|||||||
export interface GitLabRepoConnectionState extends IConnectionState {
|
export interface GitLabRepoConnectionState extends IConnectionState {
|
||||||
instance: string;
|
instance: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
enableHooks?: AllowedEventsNames[],
|
||||||
|
/**
|
||||||
|
* Do not use. Use `enableHooks`
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
ignoreHooks?: AllowedEventsNames[],
|
ignoreHooks?: AllowedEventsNames[],
|
||||||
includeCommentBody?: boolean;
|
includeCommentBody?: boolean;
|
||||||
pushTagsRegex?: string,
|
pushTagsRegex?: string,
|
||||||
@ -28,6 +33,11 @@ export interface GitLabRepoConnectionState extends IConnectionState {
|
|||||||
excludingLabels?: string[];
|
excludingLabels?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ConnectionStateValidated extends GitLabRepoConnectionState {
|
||||||
|
ignoreHooks: undefined,
|
||||||
|
enableHooks: AllowedEventsNames[],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface GitLabRepoConnectionInstanceTarget {
|
export interface GitLabRepoConnectionInstanceTarget {
|
||||||
name: string;
|
name: string;
|
||||||
@ -80,6 +90,8 @@ const AllowedEvents: AllowedEventsNames[] = [
|
|||||||
"release.created",
|
"release.created",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const DefaultHooks = AllowedEvents;
|
||||||
|
|
||||||
const ConnectionStateSchema = {
|
const ConnectionStateSchema = {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
@ -89,6 +101,10 @@ const ConnectionStateSchema = {
|
|||||||
},
|
},
|
||||||
instance: { type: "string" },
|
instance: { type: "string" },
|
||||||
path: { type: "string" },
|
path: { type: "string" },
|
||||||
|
/**
|
||||||
|
* Do not use. Use `enableHooks`
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
ignoreHooks: {
|
ignoreHooks: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
@ -96,6 +112,13 @@ const ConnectionStateSchema = {
|
|||||||
},
|
},
|
||||||
nullable: true,
|
nullable: true,
|
||||||
},
|
},
|
||||||
|
enableHooks: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
commandPrefix: {
|
commandPrefix: {
|
||||||
type: "string",
|
type: "string",
|
||||||
minLength: 2,
|
minLength: 2,
|
||||||
@ -139,7 +162,7 @@ export interface GitLabTargetFilter {
|
|||||||
* Handles rooms connected to a GitLab repo.
|
* Handles rooms connected to a GitLab repo.
|
||||||
*/
|
*/
|
||||||
@Connection
|
@Connection
|
||||||
export class GitLabRepoConnection extends CommandConnection<GitLabRepoConnectionState> implements IConnection {
|
export class GitLabRepoConnection extends CommandConnection<GitLabRepoConnectionState, ConnectionStateValidated> implements IConnection {
|
||||||
static readonly CanonicalEventType = "uk.half-shot.matrix-hookshot.gitlab.repository";
|
static readonly CanonicalEventType = "uk.half-shot.matrix-hookshot.gitlab.repository";
|
||||||
static readonly LegacyCanonicalEventType = "uk.half-shot.matrix-github.gitlab.repository";
|
static readonly LegacyCanonicalEventType = "uk.half-shot.matrix-github.gitlab.repository";
|
||||||
|
|
||||||
@ -152,14 +175,25 @@ export class GitLabRepoConnection extends CommandConnection<GitLabRepoConnection
|
|||||||
static helpMessage: (cmdPrefix?: string | undefined) => MatrixMessageContent;
|
static helpMessage: (cmdPrefix?: string | undefined) => MatrixMessageContent;
|
||||||
static ServiceCategory = "gitlab";
|
static ServiceCategory = "gitlab";
|
||||||
|
|
||||||
static validateState(state: unknown, isExistingState = false): GitLabRepoConnectionState {
|
static validateState(state: unknown, isExistingState = false): ConnectionStateValidated {
|
||||||
const validator = new Ajv({ strict: false }).compile(ConnectionStateSchema);
|
const validator = new Ajv({ strict: false }).compile(ConnectionStateSchema);
|
||||||
if (validator(state)) {
|
if (validator(state)) {
|
||||||
// Validate ignoreHooks IF this is an incoming update (we can be less strict for existing state)
|
// Validate enableHooks IF this is an incoming update (we can be less strict for existing state)
|
||||||
if (!isExistingState && state.ignoreHooks && !state.ignoreHooks.every(h => AllowedEvents.includes(h))) {
|
if (!isExistingState && state.enableHooks && !state.enableHooks.every(h => AllowedEvents.includes(h))) {
|
||||||
throw new ApiError('`ignoreHooks` must only contain allowed values', ErrCode.BadValue);
|
throw new ApiError('`enableHooks` must only contain allowed values', ErrCode.BadValue);
|
||||||
}
|
}
|
||||||
return state;
|
if (state.ignoreHooks) {
|
||||||
|
if (!isExistingState) {
|
||||||
|
throw new ApiError('`ignoreHooks` cannot be used with new connections', ErrCode.BadValue);
|
||||||
|
}
|
||||||
|
log.warn(`Room has old state key 'ignoreHooks'. Converting to compatible enabledHooks filter`);
|
||||||
|
state.enableHooks = HookFilter.convertIgnoredHooksToEnabledHooks(state.enableHooks, state.ignoreHooks, AllowedEvents);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
enableHooks: state.enableHooks ?? AllowedEvents,
|
||||||
|
ignoreHooks: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
throw new ValidatorApiError(validator.errors);
|
throw new ValidatorApiError(validator.errors);
|
||||||
}
|
}
|
||||||
@ -309,30 +343,28 @@ export class GitLabRepoConnection extends CommandConnection<GitLabRepoConnection
|
|||||||
constructor(roomId: string,
|
constructor(roomId: string,
|
||||||
stateKey: string,
|
stateKey: string,
|
||||||
private readonly as: Appservice,
|
private readonly as: Appservice,
|
||||||
state: GitLabRepoConnectionState,
|
state: ConnectionStateValidated,
|
||||||
private readonly tokenStore: UserTokenStore,
|
private readonly tokenStore: UserTokenStore,
|
||||||
private readonly instance: GitLabInstance) {
|
private readonly instance: GitLabInstance
|
||||||
super(
|
) {
|
||||||
roomId,
|
super(
|
||||||
stateKey,
|
roomId,
|
||||||
GitLabRepoConnection.CanonicalEventType,
|
stateKey,
|
||||||
state,
|
GitLabRepoConnection.CanonicalEventType,
|
||||||
as.botClient,
|
state,
|
||||||
GitLabRepoConnection.botCommands,
|
as.botClient,
|
||||||
GitLabRepoConnection.helpMessage,
|
GitLabRepoConnection.botCommands,
|
||||||
"!gl",
|
GitLabRepoConnection.helpMessage,
|
||||||
"gitlab",
|
"!gl",
|
||||||
)
|
"gitlab",
|
||||||
if (!state.path || !state.instance) {
|
)
|
||||||
throw Error('Invalid state, missing `path` or `instance`');
|
if (!state.path || !state.instance) {
|
||||||
}
|
throw Error('Invalid state, missing `path` or `instance`');
|
||||||
this.hookFilter = new HookFilter(
|
}
|
||||||
// GitLab allows all events by default
|
this.hookFilter = new HookFilter(
|
||||||
AllowedEvents,
|
state.enableHooks ?? DefaultHooks,
|
||||||
[],
|
);
|
||||||
state.ignoreHooks,
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get path() {
|
public get path() {
|
||||||
return this.state.path.toLowerCase();
|
return this.state.path.toLowerCase();
|
||||||
@ -361,7 +393,7 @@ export class GitLabRepoConnection extends CommandConnection<GitLabRepoConnection
|
|||||||
|
|
||||||
public async onStateUpdate(stateEv: MatrixEvent<unknown>) {
|
public async onStateUpdate(stateEv: MatrixEvent<unknown>) {
|
||||||
await super.onStateUpdate(stateEv);
|
await super.onStateUpdate(stateEv);
|
||||||
this.hookFilter.ignoredHooks = this.state.ignoreHooks ?? [];
|
this.hookFilter.enabledHooks = this.state.enableHooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getProvisionerDetails(): GitLabRepoResponseItem {
|
public getProvisionerDetails(): GitLabRepoResponseItem {
|
||||||
@ -780,7 +812,7 @@ ${data.description}`;
|
|||||||
const validatedConfig = GitLabRepoConnection.validateState(config);
|
const validatedConfig = GitLabRepoConnection.validateState(config);
|
||||||
await this.as.botClient.sendStateEvent(this.roomId, GitLabRepoConnection.CanonicalEventType, this.stateKey, validatedConfig);
|
await this.as.botClient.sendStateEvent(this.roomId, GitLabRepoConnection.CanonicalEventType, this.stateKey, validatedConfig);
|
||||||
this.state = validatedConfig;
|
this.state = validatedConfig;
|
||||||
this.hookFilter.ignoredHooks = this.state.ignoreHooks ?? [];
|
this.hookFilter.enabledHooks = this.state.enableHooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onRemove() {
|
public async onRemove() {
|
||||||
|
@ -81,7 +81,7 @@ export interface ConnectionDeclaration<C extends IConnection = IConnection> {
|
|||||||
EventTypes: string[];
|
EventTypes: string[];
|
||||||
ServiceCategory: string;
|
ServiceCategory: string;
|
||||||
provisionConnection?: (roomId: string, userId: string, data: Record<string, unknown>, opts: ProvisionConnectionOpts) => Promise<{connection: C, warning?: ConnectionWarning}>;
|
provisionConnection?: (roomId: string, userId: string, data: Record<string, unknown>, opts: ProvisionConnectionOpts) => Promise<{connection: C, warning?: ConnectionWarning}>;
|
||||||
createConnectionForState: (roomId: string, state: StateEvent<Record<string, unknown>>, opts: InstantiateConnectionOpts) => C|Promise<C>
|
createConnectionForState: (roomId: string, state: StateEvent<Record<string, unknown>>, opts: InstantiateConnectionOpts) => C|Promise<C>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConnectionDeclarations: Array<ConnectionDeclaration> = [];
|
export const ConnectionDeclarations: Array<ConnectionDeclaration> = [];
|
||||||
|
@ -106,7 +106,7 @@ export class JiraProjectConnection extends CommandConnection<JiraProjectConnecti
|
|||||||
static botCommands: BotCommands;
|
static botCommands: BotCommands;
|
||||||
static helpMessage: (cmdPrefix?: string) => MatrixMessageContent;
|
static helpMessage: (cmdPrefix?: string) => MatrixMessageContent;
|
||||||
|
|
||||||
static async provisionConnection(roomId: string, userId: string, data: Record<string, unknown>, {getAllConnectionsOfType, as, tokenStore, config}: ProvisionConnectionOpts) {
|
static async provisionConnection(roomId: string, userId: string, data: Record<string, unknown>, {as, tokenStore, config}: ProvisionConnectionOpts) {
|
||||||
if (!config.jira) {
|
if (!config.jira) {
|
||||||
throw new ApiError('JIRA integration is not configured', ErrCode.DisabledFeature);
|
throw new ApiError('JIRA integration is not configured', ErrCode.DisabledFeature);
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,32 @@
|
|||||||
export class HookFilter<T extends string> {
|
export class HookFilter<T extends string> {
|
||||||
|
static convertIgnoredHooksToEnabledHooks<T extends string>(explicitlyEnabledHooks: T[] = [], ignoredHooks: T[], defaultHooks: T[]): T[] {
|
||||||
|
const resultHookSet = new Set([
|
||||||
|
...explicitlyEnabledHooks,
|
||||||
|
...defaultHooks,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// For each ignored hook, remove anything that matches.
|
||||||
|
for (const ignoredHook of ignoredHooks) {
|
||||||
|
resultHookSet.delete(ignoredHook);
|
||||||
|
// If the hook is a "root" hook name, remove all children.
|
||||||
|
for (const enabledHook of resultHookSet) {
|
||||||
|
if (enabledHook.startsWith(`${ignoredHook}.`)) {
|
||||||
|
resultHookSet.delete(enabledHook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...resultHookSet];
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly defaultHooks: T[],
|
|
||||||
public enabledHooks: T[] = [],
|
public enabledHooks: T[] = [],
|
||||||
public ignoredHooks: T[] = []
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public shouldSkip(...hookName: T[]) {
|
public shouldSkip(...hookName: T[]) {
|
||||||
if (hookName.some(name => this.ignoredHooks.includes(name))) {
|
// Should skip if all of the hook names are missing
|
||||||
return true;
|
return hookName.every(name => !this.enabledHooks.includes(name));
|
||||||
}
|
|
||||||
if (hookName.some(name => this.enabledHooks.includes(name))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return !hookName.some(h => this.defaultHooks.includes(h));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,29 +3,51 @@ import { HookFilter } from '../src/HookFilter';
|
|||||||
|
|
||||||
const DEFAULT_SET = ['default-allowed', 'default-allowed-but-ignored'];
|
const DEFAULT_SET = ['default-allowed', 'default-allowed-but-ignored'];
|
||||||
const ENABLED_SET = ['enabled-hook', 'enabled-but-ignored'];
|
const ENABLED_SET = ['enabled-hook', 'enabled-but-ignored'];
|
||||||
const IGNORED_SET = ['ignored', 'enabled-but-ignored', 'default-allowed-but-ignored'];
|
|
||||||
|
|
||||||
describe("HookFilter", () => {
|
describe("HookFilter", () => {
|
||||||
let filter: HookFilter<string>;
|
let filter: HookFilter<string>;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
filter = new HookFilter(DEFAULT_SET, ENABLED_SET, IGNORED_SET);
|
filter = new HookFilter(ENABLED_SET);
|
||||||
});
|
});
|
||||||
it('should skip a hook named in ignoreHooks', () => {
|
describe('shouldSkip', () => {
|
||||||
expect(filter.shouldSkip('ignored')).to.be.true;
|
it('should allow a hook named in enabled set', () => {
|
||||||
|
expect(filter.shouldSkip('enabled-hook')).to.be.false;
|
||||||
|
});
|
||||||
|
it('should not allow a hook not named in enabled set', () => {
|
||||||
|
expect(filter.shouldSkip('not-enabled-hook')).to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
it('should allow a hook named in defaults', () => {
|
|
||||||
expect(filter.shouldSkip('default-allowed')).to.be.false;
|
describe('convertIgnoredHooksToEnabledHooks', () => {
|
||||||
});
|
it('should correctly provide a list of default hooks', () => {
|
||||||
it('should allow a hook named in enabled', () => {
|
expect(HookFilter.convertIgnoredHooksToEnabledHooks([], [], DEFAULT_SET)).to.have.members(DEFAULT_SET);
|
||||||
expect(filter.shouldSkip('enabled-hook')).to.be.false;
|
});
|
||||||
});
|
|
||||||
it('should skip a hook named in defaults but also in ignored', () => {
|
it('should correctly include default and enabled hooks when ignored hooks is set', () => {
|
||||||
expect(filter.shouldSkip('default-allowed-but-ignored')).to.be.true;
|
expect(HookFilter.convertIgnoredHooksToEnabledHooks(ENABLED_SET, ['my-ignored-hook'], DEFAULT_SET)).to.have.members([
|
||||||
});
|
...ENABLED_SET, ...DEFAULT_SET
|
||||||
it('should skip a hook named in enabled but also in ignored', () => {
|
]);
|
||||||
expect(filter.shouldSkip('enabled-but-ignored')).to.be.true;
|
});
|
||||||
});
|
|
||||||
it('should skip if any hooks are in ignored', () => {
|
it('should deduplicate', () => {
|
||||||
expect(filter.shouldSkip('enabled-hook', 'enabled-but-ignored')).to.be.true;
|
expect(HookFilter.convertIgnoredHooksToEnabledHooks(DEFAULT_SET, [], DEFAULT_SET)).to.have.members(DEFAULT_SET);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly exclude ignored hooks', () => {
|
||||||
|
expect(HookFilter.convertIgnoredHooksToEnabledHooks([], [DEFAULT_SET[0]], DEFAULT_SET)).to.not.include([
|
||||||
|
DEFAULT_SET[0]
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle ignored root hooks', () => {
|
||||||
|
const defaultHooks = ['myhook', 'myhook.foo', 'myhook.foo.bar'];
|
||||||
|
expect(HookFilter.convertIgnoredHooksToEnabledHooks([], ['myhook.foo.bar'], defaultHooks)).to.have.members([
|
||||||
|
'myhook', 'myhook.foo'
|
||||||
|
]);
|
||||||
|
expect(HookFilter.convertIgnoredHooksToEnabledHooks([], ['myhook.foo'], defaultHooks)).to.have.members([
|
||||||
|
'myhook'
|
||||||
|
]);
|
||||||
|
expect(HookFilter.convertIgnoredHooksToEnabledHooks([], ['myhook'], defaultHooks)).to.be.empty;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import { UserTokenStore } from "../../src/UserTokenStore";
|
|||||||
import { DefaultConfig } from "../../src/Config/Defaults";
|
import { DefaultConfig } from "../../src/Config/Defaults";
|
||||||
import { AppserviceMock } from "../utils/AppserviceMock";
|
import { AppserviceMock } from "../utils/AppserviceMock";
|
||||||
import { ApiError, ErrCode, ValidatorApiError } from "../../src/api";
|
import { ApiError, ErrCode, ValidatorApiError } from "../../src/api";
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
const ROOM_ID = "!foo:bar";
|
const ROOM_ID = "!foo:bar";
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ describe("GitHubRepoConnection", () => {
|
|||||||
GitHubRepoConnection.validateState({
|
GitHubRepoConnection.validateState({
|
||||||
org: "foo",
|
org: "foo",
|
||||||
repo: "bar",
|
repo: "bar",
|
||||||
ignoreHooks: ["issue", "pull_request", "release"],
|
enableHooks: ["issue", "pull_request", "release"],
|
||||||
commandPrefix: "!foo",
|
commandPrefix: "!foo",
|
||||||
showIssueRoomLink: true,
|
showIssueRoomLink: true,
|
||||||
prDiff: {
|
prDiff: {
|
||||||
@ -81,6 +82,16 @@ describe("GitHubRepoConnection", () => {
|
|||||||
}
|
}
|
||||||
} as GitHubRepoConnectionState as unknown as Record<string, unknown>);
|
} as GitHubRepoConnectionState as unknown as Record<string, unknown>);
|
||||||
});
|
});
|
||||||
|
it("will convert ignoredHooks for existing state", () => {
|
||||||
|
const state = GitHubRepoConnection.validateState({
|
||||||
|
org: "foo",
|
||||||
|
repo: "bar",
|
||||||
|
ignoreHooks: ["issue"],
|
||||||
|
enableHooks: ["issue", "pull_request", "release"],
|
||||||
|
commandPrefix: "!foo",
|
||||||
|
} as GitHubRepoConnectionState as unknown as Record<string, unknown>, true);
|
||||||
|
expect(state.enableHooks).to.not.contain('issue');
|
||||||
|
});
|
||||||
it("will disallow invalid state", () => {
|
it("will disallow invalid state", () => {
|
||||||
try {
|
try {
|
||||||
GitHubRepoConnection.validateState({
|
GitHubRepoConnection.validateState({
|
||||||
@ -93,12 +104,12 @@ describe("GitHubRepoConnection", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it("will disallow ignoreHooks to contains invalid enums if this is new state", () => {
|
it("will disallow enabledHooks to contains invalid enums if this is new state", () => {
|
||||||
try {
|
try {
|
||||||
GitHubRepoConnection.validateState({
|
GitHubRepoConnection.validateState({
|
||||||
org: "foo",
|
org: "foo",
|
||||||
repo: "bar",
|
repo: "bar",
|
||||||
ignoreHooks: ["issue", "pull_request", "release", "not-real"],
|
enabledHooks: ["not-real"],
|
||||||
}, false);
|
}, false);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (ex instanceof ApiError === false || ex.errcode !== ErrCode.BadValue) {
|
if (ex instanceof ApiError === false || ex.errcode !== ErrCode.BadValue) {
|
||||||
@ -106,11 +117,11 @@ describe("GitHubRepoConnection", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it("will allow ignoreHooks to contains invalid enums if this is old state", () => {
|
it("will allow enabledHooks to contains invalid enums if this is old state", () => {
|
||||||
GitHubRepoConnection.validateState({
|
GitHubRepoConnection.validateState({
|
||||||
org: "foo",
|
org: "foo",
|
||||||
repo: "bar",
|
repo: "bar",
|
||||||
ignoreHooks: ["issue", "pull_request", "release", "not-real"],
|
enabledHooks: ["not-real"],
|
||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import { UserTokenStore } from "../../src/UserTokenStore";
|
|||||||
import { AppserviceMock } from "../utils/AppserviceMock";
|
import { AppserviceMock } from "../utils/AppserviceMock";
|
||||||
import { ApiError, ErrCode, ValidatorApiError } from "../../src/api";
|
import { ApiError, ErrCode, ValidatorApiError } from "../../src/api";
|
||||||
import { GitLabRepoConnection, GitLabRepoConnectionState } from "../../src/Connections";
|
import { GitLabRepoConnection, GitLabRepoConnectionState } from "../../src/Connections";
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
const ROOM_ID = "!foo:bar";
|
const ROOM_ID = "!foo:bar";
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ describe("GitLabRepoConnection", () => {
|
|||||||
GitLabRepoConnection.validateState({
|
GitLabRepoConnection.validateState({
|
||||||
instance: "foo",
|
instance: "foo",
|
||||||
path: "bar/baz",
|
path: "bar/baz",
|
||||||
ignoreHooks: [
|
enableHooks: [
|
||||||
"merge_request.open",
|
"merge_request.open",
|
||||||
"merge_request.close",
|
"merge_request.close",
|
||||||
"merge_request.merge",
|
"merge_request.merge",
|
||||||
@ -79,6 +80,17 @@ describe("GitLabRepoConnection", () => {
|
|||||||
excludingLabels: ["but-not-me"],
|
excludingLabels: ["but-not-me"],
|
||||||
} as GitLabRepoConnectionState as unknown as Record<string, unknown>);
|
} as GitLabRepoConnectionState as unknown as Record<string, unknown>);
|
||||||
});
|
});
|
||||||
|
it("will convert ignoredHooks for existing state", () => {
|
||||||
|
const state = GitLabRepoConnection.validateState({
|
||||||
|
instance: "foo",
|
||||||
|
path: "bar/baz",
|
||||||
|
ignoreHooks: [
|
||||||
|
"merge_request",
|
||||||
|
],
|
||||||
|
commandPrefix: "!gl",
|
||||||
|
} as GitLabRepoConnectionState as unknown as Record<string, unknown>, true);
|
||||||
|
expect(state.enableHooks).to.not.contain('merge_request');
|
||||||
|
});
|
||||||
it("will disallow invalid state", () => {
|
it("will disallow invalid state", () => {
|
||||||
try {
|
try {
|
||||||
GitLabRepoConnection.validateState({
|
GitLabRepoConnection.validateState({
|
||||||
@ -91,12 +103,12 @@ describe("GitLabRepoConnection", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it("will disallow ignoreHooks to contains invalid enums if this is new state", () => {
|
it("will disallow enabledHooks to contains invalid enums if this is new state", () => {
|
||||||
try {
|
try {
|
||||||
GitLabRepoConnection.validateState({
|
GitLabRepoConnection.validateState({
|
||||||
instance: "bar",
|
instance: "bar",
|
||||||
path: "foo",
|
path: "foo",
|
||||||
ignoreHooks: ["issue", "pull_request", "release", "not-real"],
|
enabledHooks: ["not-real"],
|
||||||
}, false);
|
}, false);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (ex instanceof ApiError === false || ex.errcode !== ErrCode.BadValue) {
|
if (ex instanceof ApiError === false || ex.errcode !== ErrCode.BadValue) {
|
||||||
@ -104,11 +116,11 @@ describe("GitLabRepoConnection", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it("will allow ignoreHooks to contains invalid enums if this is old state", () => {
|
it("will allow enabledHooks to contains invalid enums if this is old state", () => {
|
||||||
GitLabRepoConnection.validateState({
|
GitLabRepoConnection.validateState({
|
||||||
instance: "bar",
|
instance: "bar",
|
||||||
path: "foo",
|
path: "foo",
|
||||||
ignoreHooks: ["issues", "merge_request", "foo"],
|
enabledHooks: ["not-real"],
|
||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { h, Component } from 'preact';
|
import { Component } from 'preact';
|
||||||
import WA, { MatrixCapabilities } from 'matrix-widget-api';
|
import WA, { MatrixCapabilities } from 'matrix-widget-api';
|
||||||
import { BridgeAPI, BridgeAPIError } from './BridgeAPI';
|
import { BridgeAPI, BridgeAPIError } from './BridgeAPI';
|
||||||
import { BridgeRoomState } from '../src/Widgets/BridgeWidgetInterface';
|
import { BridgeRoomState } from '../src/Widgets/BridgeWidgetInterface';
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import { useEffect, useState, useCallback } from 'preact/hooks';
|
import { useEffect, useState, useCallback } from 'preact/hooks';
|
||||||
import { BridgeRoomState } from "../../src/Widgets/BridgeWidgetInterface";
|
import { BridgeRoomState } from "../../src/Widgets/BridgeWidgetInterface";
|
||||||
import GeneralConfig from './configs/GeneralConfig';
|
import GeneralConfig from './configs/GeneralConfig';
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import style from "./ConnectionCard.module.scss";
|
import style from "./ConnectionCard.module.scss";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { BridgeRoomStateGitHub } from '../../src/Widgets/BridgeWidgetInterface';
|
import { BridgeRoomStateGitHub } from '../../src/Widgets/BridgeWidgetInterface';
|
||||||
import "./GitHubState.css";
|
import "./GitHubState.css";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent } from "preact";
|
import { FunctionComponent } from "preact";
|
||||||
import style from "./ServiceCard.module.scss";
|
import style from "./ServiceCard.module.scss";
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { h } from "preact";
|
|
||||||
import { Button } from "../elements";
|
import { Button } from "../elements";
|
||||||
|
|
||||||
export default function GeneralConfig() {
|
export default function GeneralConfig() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent } from "preact";
|
import { FunctionComponent } from "preact";
|
||||||
import ErrorBadge from "../../icons/error-badge.svg";
|
import ErrorBadge from "../../icons/error-badge.svg";
|
||||||
import style from "./ErrorPane.module.scss";
|
import style from "./ErrorPane.module.scss";
|
||||||
|
|
||||||
|
22
web/components/elements/EventHookCheckbox.tsx
Normal file
22
web/components/elements/EventHookCheckbox.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { FunctionComponent } from "preact";
|
||||||
|
import { JSXInternal } from "preact/src/jsx";
|
||||||
|
|
||||||
|
export const EventHookCheckbox: FunctionComponent<{
|
||||||
|
enabledHooks: string[],
|
||||||
|
onChange: JSXInternal.GenericEventHandler<HTMLInputElement>,
|
||||||
|
hookEventName: string,
|
||||||
|
parentEvent?: string,
|
||||||
|
}> = ({enabledHooks, onChange, hookEventName, parentEvent, children}) => {
|
||||||
|
const checked = enabledHooks.includes(hookEventName) || (!!parentEvent && enabledHooks.includes(parentEvent));
|
||||||
|
|
||||||
|
return <li>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
x-event-name={hookEventName}
|
||||||
|
checked={checked}
|
||||||
|
onChange={onChange} />
|
||||||
|
{ children }
|
||||||
|
</label>
|
||||||
|
</li>;
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent } from "preact";
|
import { FunctionComponent } from "preact";
|
||||||
import style from "./InputField.module.scss";
|
import style from "./InputField.module.scss";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent } from "preact"
|
import { FunctionComponent } from "preact"
|
||||||
import { useState } from "preact/hooks"
|
import { useState } from "preact/hooks"
|
||||||
import style from "./ListItem.module.scss";
|
import style from "./ListItem.module.scss";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent } from "preact";
|
import { FunctionComponent } from "preact";
|
||||||
import WarningBadge from "../../icons/warning-badge.svg";
|
import WarningBadge from "../../icons/warning-badge.svg";
|
||||||
import style from "./WarningPane.module.scss";
|
import style from "./WarningPane.module.scss";
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent, createRef } from "preact";
|
import { FunctionComponent, createRef } from "preact";
|
||||||
import { useCallback } from "preact/hooks"
|
import { useCallback } from "preact/hooks"
|
||||||
import { BridgeConfig } from "../../BridgeAPI";
|
import { BridgeConfig } from "../../BridgeAPI";
|
||||||
import { FeedConnectionState, FeedResponseItem } from "../../../src/Connections/FeedConnection";
|
import { FeedConnectionState, FeedResponseItem } from "../../../src/Connections/FeedConnection";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent, createRef } from "preact";
|
import { FunctionComponent, createRef } from "preact";
|
||||||
import { useCallback, useState } from "preact/hooks"
|
import { useCallback, useState } from "preact/hooks"
|
||||||
import CodeMirror from '@uiw/react-codemirror';
|
import CodeMirror from '@uiw/react-codemirror';
|
||||||
import { javascript } from '@codemirror/lang-javascript';
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { h, FunctionComponent, createRef } from "preact";
|
import GitHubIcon from "../../icons/github.png";
|
||||||
import { useState, useCallback, useEffect, useMemo } from "preact/hooks";
|
|
||||||
import { BridgeAPI, BridgeConfig } from "../../BridgeAPI";
|
import { BridgeAPI, BridgeConfig } from "../../BridgeAPI";
|
||||||
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
|
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
|
||||||
import { ErrCode } from "../../../src/api";
|
import { ErrCode } from "../../../src/api";
|
||||||
|
import { EventHookCheckbox } from '../elements/EventHookCheckbox';
|
||||||
|
import { FunctionComponent, createRef } from "preact";
|
||||||
import { GitHubRepoConnectionState, GitHubRepoResponseItem, GitHubRepoConnectionRepoTarget, GitHubTargetFilter, GitHubRepoConnectionOrgTarget } from "../../../src/Connections/GithubRepo";
|
import { GitHubRepoConnectionState, GitHubRepoResponseItem, GitHubRepoConnectionRepoTarget, GitHubTargetFilter, GitHubRepoConnectionOrgTarget } from "../../../src/Connections/GithubRepo";
|
||||||
import { InputField, ButtonSet, Button, ErrorPane } from "../elements";
|
import { InputField, ButtonSet, Button, ErrorPane } from "../elements";
|
||||||
import GitHubIcon from "../../icons/github.png";
|
import { useState, useCallback, useEffect, useMemo } from "preact/hooks";
|
||||||
|
|
||||||
const EventType = "uk.half-shot.matrix-hookshot.github.repository";
|
const EventType = "uk.half-shot.matrix-hookshot.github.repository";
|
||||||
const NUM_REPOS_PER_PAGE = 10;
|
const NUM_REPOS_PER_PAGE = 10;
|
||||||
@ -119,76 +120,15 @@ const ConnectionSearch: FunctionComponent<{api: BridgeAPI, onPicked: (state: Git
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventCheckbox: FunctionComponent<{
|
|
||||||
ignoredHooks?: string[],
|
|
||||||
enabledHooks?: string[],
|
|
||||||
onChange: (evt: HTMLInputElement) => void,
|
|
||||||
eventName: string,
|
|
||||||
parentEvent?: string,
|
|
||||||
}> = ({ignoredHooks, enabledHooks, onChange, eventName, parentEvent, children}) => {
|
|
||||||
let disabled = false;
|
|
||||||
let checked = false;
|
|
||||||
|
|
||||||
if (!enabledHooks && !ignoredHooks) {
|
|
||||||
throw Error(`Invalid configuration for checkbox ${eventName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enabledHooks) {
|
|
||||||
disabled = !!(parentEvent && !enabledHooks.includes(parentEvent));
|
|
||||||
checked = enabledHooks.includes(eventName);
|
|
||||||
if (ignoredHooks?.includes(eventName)) {
|
|
||||||
// If both are set, this was previously a on-by-default event
|
|
||||||
// that is now off-by-default, and so we need to check both fields.
|
|
||||||
disabled = !!(parentEvent && ignoredHooks.includes(parentEvent));
|
|
||||||
checked = true
|
|
||||||
}
|
|
||||||
} else if (ignoredHooks) {
|
|
||||||
// If enabled hooks is not set, this is on-by-default hook.
|
|
||||||
disabled = !!(parentEvent && ignoredHooks.includes(parentEvent));
|
|
||||||
checked = !ignoredHooks.includes(eventName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <li>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
disabled={disabled}
|
|
||||||
type="checkbox"
|
|
||||||
x-event-name={eventName}
|
|
||||||
checked={checked}
|
|
||||||
onChange={onChange} />
|
|
||||||
{ children }
|
|
||||||
</label>
|
|
||||||
</li>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitHubRepoResponseItem, GitHubRepoConnectionState>> = ({api, existingConnection, onSave, onRemove }) => {
|
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitHubRepoResponseItem, GitHubRepoConnectionState>> = ({api, existingConnection, onSave, onRemove }) => {
|
||||||
const [ignoredHooks, setIgnoredHooks] = useState<string[]>(existingConnection?.config.ignoreHooks || []);
|
|
||||||
// Only used for off-by-default hooks.
|
|
||||||
const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
|
const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
|
||||||
|
|
||||||
const toggleIgnoredHook = useCallback(evt => {
|
const toggleEnabledHook = useCallback((evt: any) => {
|
||||||
const key = (evt.target as HTMLElement).getAttribute('x-event-name');
|
|
||||||
if (key) {
|
|
||||||
setIgnoredHooks(ignoredHooks => (
|
|
||||||
ignoredHooks.includes(key) ? ignoredHooks.filter(k => k !== key) : [...ignoredHooks, key]
|
|
||||||
));
|
|
||||||
// Remove from enabledHooks
|
|
||||||
setEnabledHooks(enabledHooks => (
|
|
||||||
enabledHooks.filter(k => k !== key)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleEnabledHook = useCallback(evt => {
|
|
||||||
const key = (evt.target as HTMLElement).getAttribute('x-event-name');
|
const key = (evt.target as HTMLElement).getAttribute('x-event-name');
|
||||||
if (key) {
|
if (key) {
|
||||||
setEnabledHooks(enabledHooks => (
|
setEnabledHooks(enabledHooks => (
|
||||||
enabledHooks.includes(key) ? enabledHooks.filter(k => k !== key) : [...enabledHooks, key]
|
enabledHooks.includes(key) ? enabledHooks.filter(k => k !== key) : [...enabledHooks, key]
|
||||||
));
|
));
|
||||||
// Remove from ignoreHooks
|
|
||||||
setIgnoredHooks(ignoredHooks => (
|
|
||||||
ignoredHooks.filter(k => k !== key)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -205,12 +145,11 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
|||||||
if (state) {
|
if (state) {
|
||||||
onSave({
|
onSave({
|
||||||
...(state),
|
...(state),
|
||||||
ignoreHooks: ignoredHooks as any[],
|
|
||||||
enableHooks: enabledHooks as any[],
|
enableHooks: enabledHooks as any[],
|
||||||
commandPrefix: commandPrefixRef.current?.value || commandPrefixRef.current?.placeholder,
|
commandPrefix: commandPrefixRef.current?.value || commandPrefixRef.current?.placeholder,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [enabledHooks, canEdit, existingConnection, connectionState, ignoredHooks, commandPrefixRef, onSave]);
|
}, [enabledHooks, canEdit, existingConnection, connectionState, commandPrefixRef, onSave]);
|
||||||
|
|
||||||
return <form onSubmit={handleSave}>
|
return <form onSubmit={handleSave}>
|
||||||
{!existingConnection && <ConnectionSearch api={api} onPicked={setConnectionState} />}
|
{!existingConnection && <ConnectionSearch api={api} onPicked={setConnectionState} />}
|
||||||
@ -220,35 +159,35 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
|||||||
<InputField visible={!!existingConnection || !!connectionState} label="Events" noPadding={true}>
|
<InputField visible={!!existingConnection || !!connectionState} label="Events" noPadding={true}>
|
||||||
<p>Choose which event should send a notification to the room</p>
|
<p>Choose which event should send a notification to the room</p>
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} eventName="issue" onChange={toggleIgnoredHook}>Issues</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="issue" onChange={toggleEnabledHook}>Issues</EventHookCheckbox>
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="issue" eventName="issue.created" onChange={toggleIgnoredHook}>Created</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="issue" hookEventName="issue.created" onChange={toggleEnabledHook}>Created</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="issue" eventName="issue.changed" onChange={toggleIgnoredHook}>Changed</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="issue" hookEventName="issue.changed" onChange={toggleEnabledHook}>Changed</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="issue" eventName="issue.edited" onChange={toggleIgnoredHook}>Edited</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="issue" hookEventName="issue.edited" onChange={toggleEnabledHook}>Edited</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="issue" eventName="issue.labeled" onChange={toggleIgnoredHook}>Labeled</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="issue" hookEventName="issue.labeled" onChange={toggleEnabledHook}>Labeled</EventHookCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} eventName="pull_request" onChange={toggleIgnoredHook}>Pull requests</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="pull_request" onChange={toggleEnabledHook}>Pull requests</EventHookCheckbox>
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="pull_request" eventName="pull_request.opened" onChange={toggleIgnoredHook}>Opened</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="pull_request" hookEventName="pull_request.opened" onChange={toggleEnabledHook}>Opened</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="pull_request" eventName="pull_request.closed" onChange={toggleIgnoredHook}>Closed</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="pull_request" hookEventName="pull_request.closed" onChange={toggleEnabledHook}>Closed</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="pull_request" eventName="pull_request.merged" onChange={toggleIgnoredHook}>Merged</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="pull_request" hookEventName="pull_request.merged" onChange={toggleEnabledHook}>Merged</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="pull_request" eventName="pull_request.ready_for_review" onChange={toggleIgnoredHook}>Ready for review</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="pull_request" hookEventName="pull_request.ready_for_review" onChange={toggleEnabledHook}>Ready for review</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="pull_request" eventName="pull_request.reviewed" onChange={toggleIgnoredHook}>Reviewed</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="pull_request" hookEventName="pull_request.reviewed" onChange={toggleEnabledHook}>Reviewed</EventHookCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} eventName="workflow.run" onChange={toggleEnabledHook}>Workflow Runs</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="workflow.run" onChange={toggleEnabledHook}>Workflow Runs</EventHookCheckbox>
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="workflow.run" eventName="workflow.run.success" onChange={toggleEnabledHook}>Success</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="workflow.run" hookEventName="workflow.run.success" onChange={toggleEnabledHook}>Success</EventHookCheckbox>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="workflow.run" eventName="workflow.run.failure" onChange={toggleEnabledHook}>Failed</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="workflow.run" hookEventName="workflow.run.failure" onChange={toggleEnabledHook}>Failed</EventHookCheckbox>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="workflow.run" eventName="workflow.run.neutral" onChange={toggleEnabledHook}>Neutral</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="workflow.run" hookEventName="workflow.run.neutral" onChange={toggleEnabledHook}>Neutral</EventHookCheckbox>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="workflow.run" eventName="workflow.run.cancelled" onChange={toggleEnabledHook}>Cancelled</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="workflow.run" hookEventName="workflow.run.cancelled" onChange={toggleEnabledHook}>Cancelled</EventHookCheckbox>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="workflow.run" eventName="workflow.run.timed_out" onChange={toggleEnabledHook}>Timed Out</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="workflow.run" hookEventName="workflow.run.timed_out" onChange={toggleEnabledHook}>Timed Out</EventHookCheckbox>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="workflow.run" eventName="workflow.run.action_required" onChange={toggleEnabledHook}>Action Required</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="workflow.run" hookEventName="workflow.run.action_required" onChange={toggleEnabledHook}>Action Required</EventHookCheckbox>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="workflow.run" eventName="workflow.run.stale" onChange={toggleEnabledHook}>Stale</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="workflow.run" hookEventName="workflow.run.stale" onChange={toggleEnabledHook}>Stale</EventHookCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} eventName="release" onChange={toggleIgnoredHook}>Releases</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="release" onChange={toggleEnabledHook}>Releases</EventHookCheckbox>
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="release" eventName="release.created" onChange={toggleIgnoredHook}>Published</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="release" hookEventName="release.created" onChange={toggleEnabledHook}>Published</EventHookCheckbox>
|
||||||
<EventCheckbox enabledHooks={enabledHooks} ignoredHooks={ignoredHooks} parentEvent="release" eventName="release.drafted" onChange={toggleEnabledHook}>Drafted</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="release" hookEventName="release.drafted" onChange={toggleEnabledHook}>Drafted</EventHookCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { h, FunctionComponent, createRef } from "preact";
|
import GitLabIcon from "../../icons/gitlab.png";
|
||||||
import { useState, useCallback, useEffect, useMemo } from "preact/hooks";
|
|
||||||
import { BridgeAPI, BridgeConfig } from "../../BridgeAPI";
|
import { BridgeAPI, BridgeConfig } from "../../BridgeAPI";
|
||||||
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
|
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
|
||||||
|
import { EventHookCheckbox } from '../elements/EventHookCheckbox';
|
||||||
import { GitLabRepoConnectionState, GitLabRepoResponseItem, GitLabTargetFilter, GitLabRepoConnectionTarget, GitLabRepoConnectionProjectTarget, GitLabRepoConnectionInstanceTarget } from "../../../src/Connections/GitlabRepo";
|
import { GitLabRepoConnectionState, GitLabRepoResponseItem, GitLabTargetFilter, GitLabRepoConnectionTarget, GitLabRepoConnectionProjectTarget, GitLabRepoConnectionInstanceTarget } from "../../../src/Connections/GitlabRepo";
|
||||||
import { InputField, ButtonSet, Button, ErrorPane } from "../elements";
|
import { InputField, ButtonSet, Button, ErrorPane } from "../elements";
|
||||||
import GitLabIcon from "../../icons/gitlab.png";
|
import { FunctionComponent, createRef } from "preact";
|
||||||
|
import { useState, useCallback, useEffect, useMemo } from "preact/hooks";
|
||||||
|
|
||||||
const EventType = "uk.half-shot.matrix-hookshot.gitlab.repository";
|
const EventType = "uk.half-shot.matrix-hookshot.gitlab.repository";
|
||||||
|
|
||||||
@ -97,36 +98,18 @@ const ConnectionSearch: FunctionComponent<{api: BridgeAPI, onPicked: (state: Git
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventCheckbox: FunctionComponent<{
|
|
||||||
ignoredHooks: string[],
|
|
||||||
onChange: (evt: HTMLInputElement) => void,
|
|
||||||
eventName: string,
|
|
||||||
parentEvent?: string,
|
|
||||||
}> = ({ignoredHooks, onChange, eventName, parentEvent, children}) => {
|
|
||||||
return <li>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
disabled={parentEvent && ignoredHooks.includes(parentEvent)}
|
|
||||||
type="checkbox"
|
|
||||||
x-event-name={eventName}
|
|
||||||
checked={!ignoredHooks.includes(eventName)}
|
|
||||||
onChange={onChange} />
|
|
||||||
{ children }
|
|
||||||
</label>
|
|
||||||
</li>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitLabRepoResponseItem, GitLabRepoConnectionState>> = ({api, existingConnection, onSave, onRemove }) => {
|
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitLabRepoResponseItem, GitLabRepoConnectionState>> = ({api, existingConnection, onSave, onRemove }) => {
|
||||||
const [ignoredHooks, setIgnoredHooks] = useState<string[]>(existingConnection?.config.ignoreHooks || []);
|
const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
|
||||||
|
|
||||||
const toggleIgnoredHook = useCallback(evt => {
|
const toggleEnabledHook = useCallback((evt: any) => {
|
||||||
const key = (evt.target as HTMLElement).getAttribute('x-event-name');
|
const key = (evt.target as HTMLElement).getAttribute('x-event-name');
|
||||||
if (key) {
|
if (key) {
|
||||||
setIgnoredHooks(ignoredHooks => (
|
setEnabledHooks(enabledHooks => (
|
||||||
ignoredHooks.includes(key) ? ignoredHooks.filter(k => k !== key) : [...ignoredHooks, key]
|
enabledHooks.includes(key) ? enabledHooks.filter(k => k !== key) : [...enabledHooks, key]
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [newInstanceState, setNewInstanceState] = useState<GitLabRepoConnectionState|null>(null);
|
const [newInstanceState, setNewInstanceState] = useState<GitLabRepoConnectionState|null>(null);
|
||||||
|
|
||||||
const canEdit = !existingConnection || (existingConnection?.canEdit ?? false);
|
const canEdit = !existingConnection || (existingConnection?.canEdit ?? false);
|
||||||
@ -141,12 +124,12 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
|||||||
if (state) {
|
if (state) {
|
||||||
onSave({
|
onSave({
|
||||||
...(state),
|
...(state),
|
||||||
ignoreHooks: ignoredHooks as any[],
|
enableHooks: enabledHooks as any[],
|
||||||
includeCommentBody: includeBodyRef.current?.checked,
|
includeCommentBody: includeBodyRef.current?.checked,
|
||||||
commandPrefix: commandPrefixRef.current?.value || commandPrefixRef.current?.placeholder,
|
commandPrefix: commandPrefixRef.current?.value || commandPrefixRef.current?.placeholder,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [canEdit, existingConnection, newInstanceState, ignoredHooks, commandPrefixRef, onSave]);
|
}, [includeBodyRef, canEdit, existingConnection, newInstanceState, enabledHooks, commandPrefixRef, onSave]);
|
||||||
|
|
||||||
return <form onSubmit={handleSave}>
|
return <form onSubmit={handleSave}>
|
||||||
{!existingConnection && <ConnectionSearch api={api} onPicked={setNewInstanceState} />}
|
{!existingConnection && <ConnectionSearch api={api} onPicked={setNewInstanceState} />}
|
||||||
@ -165,18 +148,18 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
|||||||
<InputField visible={!!existingConnection || !!newInstanceState} label="Events" noPadding={true}>
|
<InputField visible={!!existingConnection || !!newInstanceState} label="Events" noPadding={true}>
|
||||||
<p>Choose which event should send a notification to the room</p>
|
<p>Choose which event should send a notification to the room</p>
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} eventName="merge_request" onChange={toggleIgnoredHook}>Merge requests</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="merge_request" onChange={toggleEnabledHook}>Merge requests</EventHookCheckbox>
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="merge_request" eventName="merge_request.open" onChange={toggleIgnoredHook}>Opened</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="merge_request" hookEventName="merge_request.open" onChange={toggleEnabledHook}>Opened</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="merge_request" eventName="merge_request.close" onChange={toggleIgnoredHook}>Closed</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="merge_request" hookEventName="merge_request.close" onChange={toggleEnabledHook}>Closed</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="merge_request" eventName="merge_request.merge" onChange={toggleIgnoredHook}>Merged</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="merge_request" hookEventName="merge_request.merge" onChange={toggleEnabledHook}>Merged</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="merge_request" eventName="merge_request.review" onChange={toggleIgnoredHook}>Reviewed</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="merge_request" hookEventName="merge_request.review" onChange={toggleEnabledHook}>Reviewed</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} parentEvent="merge_request" eventName="merge_request.ready_for_review" onChange={toggleIgnoredHook}>Ready for review</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} parentEvent="merge_request" hookEventName="merge_request.ready_for_review" onChange={toggleEnabledHook}>Ready for review</EventHookCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} eventName="push" onChange={toggleIgnoredHook}>Pushes</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="push" onChange={toggleEnabledHook}>Pushes</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} eventName="tag_push" onChange={toggleIgnoredHook}>Tag pushes</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="tag_push" onChange={toggleEnabledHook}>Tag pushes</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} eventName="wiki" onChange={toggleIgnoredHook}>Wiki page updates</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="wiki" onChange={toggleEnabledHook}>Wiki page updates</EventHookCheckbox>
|
||||||
<EventCheckbox ignoredHooks={ignoredHooks} eventName="release" onChange={toggleIgnoredHook}>Releases</EventCheckbox>
|
<EventHookCheckbox enabledHooks={enabledHooks} hookEventName="release" onChange={toggleEnabledHook}>Releases</EventHookCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
</InputField>
|
</InputField>
|
||||||
<ButtonSet>
|
<ButtonSet>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { h, FunctionComponent, createRef } from "preact";
|
import { FunctionComponent, createRef } from "preact";
|
||||||
import { useState, useCallback, useEffect, useMemo } from "preact/hooks";
|
import { useState, useCallback, useEffect, useMemo } from "preact/hooks";
|
||||||
import { BridgeAPI, BridgeConfig } from "../../BridgeAPI";
|
import { BridgeAPI, BridgeConfig } from "../../BridgeAPI";
|
||||||
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
|
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
|
||||||
import { ErrCode } from "../../../src/api";
|
import { ErrCode } from "../../../src/api";
|
||||||
import { JiraProjectConnectionState, JiraProjectResponseItem, JiraProjectConnectionProjectTarget, JiraTargetFilter, JiraProjectConnectionInstanceTarget, JiraProjectConnectionTarget } from "../../../src/Connections/JiraProject";
|
import { JiraProjectConnectionState, JiraProjectResponseItem, JiraProjectConnectionProjectTarget, JiraTargetFilter, JiraProjectConnectionInstanceTarget, JiraProjectConnectionTarget } from "../../../src/Connections/JiraProject";
|
||||||
import { InputField, ButtonSet, Button, ErrorPane } from "../elements";
|
import { InputField, ButtonSet, Button, ErrorPane } from "../elements";
|
||||||
|
import { EventHookCheckbox } from '../elements/EventHookCheckbox';
|
||||||
import JiraIcon from "../../icons/jira.png";
|
import JiraIcon from "../../icons/jira.png";
|
||||||
|
|
||||||
const EventType = "uk.half-shot.matrix-hookshot.jira.project";
|
const EventType = "uk.half-shot.matrix-hookshot.jira.project";
|
||||||
@ -116,23 +117,6 @@ const ConnectionSearch: FunctionComponent<{api: BridgeAPI, onPicked: (state: Jir
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventCheckbox: FunctionComponent<{
|
|
||||||
allowedEvents: string[],
|
|
||||||
onChange: (evt: HTMLInputElement) => void,
|
|
||||||
eventName: string,
|
|
||||||
}> = ({allowedEvents, onChange, eventName, children}) => {
|
|
||||||
return <li>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
x-event-name={eventName}
|
|
||||||
checked={allowedEvents.includes(eventName)}
|
|
||||||
onChange={onChange} />
|
|
||||||
{ children }
|
|
||||||
</label>
|
|
||||||
</li>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, JiraProjectResponseItem, JiraProjectConnectionState>> = ({api, existingConnection, onSave, onRemove }) => {
|
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, JiraProjectResponseItem, JiraProjectConnectionState>> = ({api, existingConnection, onSave, onRemove }) => {
|
||||||
const [allowedEvents, setAllowedEvents] = useState<string[]>(existingConnection?.config.events || ['issue_created']);
|
const [allowedEvents, setAllowedEvents] = useState<string[]>(existingConnection?.config.events || ['issue_created']);
|
||||||
|
|
||||||
@ -173,14 +157,14 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
|||||||
<ul>
|
<ul>
|
||||||
Issues
|
Issues
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox allowedEvents={allowedEvents} eventName="issue_created" onChange={toggleEvent}>Created</EventCheckbox>
|
<EventHookCheckbox enabledHooks={allowedEvents} hookEventName="issue_created" onChange={toggleEvent}>Created</EventHookCheckbox>
|
||||||
<EventCheckbox allowedEvents={allowedEvents} eventName="issue_updated" onChange={toggleEvent}>Updated</EventCheckbox>
|
<EventHookCheckbox enabledHooks={allowedEvents} hookEventName="issue_updated" onChange={toggleEvent}>Updated</EventHookCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
Versions
|
Versions
|
||||||
<ul>
|
<ul>
|
||||||
<EventCheckbox allowedEvents={allowedEvents} eventName="version_created" onChange={toggleEvent}>Created</EventCheckbox>
|
<EventHookCheckbox enabledHooks={allowedEvents} hookEventName="version_created" onChange={toggleEvent}>Created</EventHookCheckbox>
|
||||||
<EventCheckbox allowedEvents={allowedEvents} eventName="version_updated" onChange={toggleEvent}>Updated</EventCheckbox>
|
<EventHookCheckbox enabledHooks={allowedEvents} hookEventName="version_updated" onChange={toggleEvent}>Updated</EventHookCheckbox>
|
||||||
<EventCheckbox allowedEvents={allowedEvents} eventName="version_released" onChange={toggleEvent}>Released</EventCheckbox>
|
<EventHookCheckbox enabledHooks={allowedEvents} hookEventName="version_released" onChange={toggleEvent}>Released</EventHookCheckbox>
|
||||||
</ul>
|
</ul>
|
||||||
</ul>
|
</ul>
|
||||||
</InputField>
|
</InputField>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, FunctionComponent } from "preact";
|
import { FunctionComponent } from "preact";
|
||||||
import { useCallback, useEffect, useReducer, useState } from "preact/hooks"
|
import { useCallback, useEffect, useReducer, useState } from "preact/hooks"
|
||||||
import { BridgeAPI, BridgeAPIError } from "../../BridgeAPI";
|
import { BridgeAPI, BridgeAPIError } from "../../BridgeAPI";
|
||||||
import { ErrorPane, ListItem, WarningPane } from "../elements";
|
import { ErrorPane, ListItem, WarningPane } from "../elements";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { h, render } from 'preact';
|
import { render } from 'preact';
|
||||||
import 'preact/devtools';
|
import 'preact/devtools';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import "./fonts/fonts.scss"
|
import "./fonts/fonts.scss"
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "preact",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es2019",
|
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"jsx": "preserve",
|
"target": "es2019",
|
||||||
"jsxFactory": "h",
|
"types": ["preact"],
|
||||||
/* noEmit - Snowpack builds (emits) files, not tsc. */
|
/* noEmit - Vite builds (emits) files, not tsc. */
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
/* Additional Options */
|
/* Additional Options */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user