feat(workflows): add "attribute action" action
This action lets users perform several actions on threads, such as
(un)lock, (un)set as trending, (un)pin, set as non-issue, obsolete, etc.
The action selector shows the action with the codename it has in the
Protobuf enum. We will show a friendly string when we localize the
feature.
Bug: twpowertools:74
Change-Id: I95f9f1904ffe559c92a786cbdb327613c8ca32ca
diff --git a/src/contentScripts/communityConsole/workflows/actionRunners/attribute.js b/src/contentScripts/communityConsole/workflows/actionRunners/attribute.js
new file mode 100644
index 0000000..bec2511
--- /dev/null
+++ b/src/contentScripts/communityConsole/workflows/actionRunners/attribute.js
@@ -0,0 +1,20 @@
+import {CCApi} from '../../../../common/api.js';
+import {getAuthUser} from '../../../../common/communityConsoleUtils.js';
+
+export default class AttributeRunner {
+ async execute(attributeAction, thread) {
+ if (!attributeAction) {
+ throw new Error(
+ 'The workflow is malformed. The attribute action is missing.');
+ }
+ const action = attributeAction.getAttributeAction();
+
+ return await CCApi(
+ 'SetThreadAttribute', {
+ 1: thread.forumId,
+ 2: thread.threadId,
+ 3: action,
+ },
+ /* authenticated = */ true, getAuthUser());
+ }
+}
diff --git a/src/contentScripts/communityConsole/workflows/runner.js b/src/contentScripts/communityConsole/workflows/runner.js
index 35c291c..164d226 100644
--- a/src/contentScripts/communityConsole/workflows/runner.js
+++ b/src/contentScripts/communityConsole/workflows/runner.js
@@ -1,8 +1,9 @@
-import {parseUrl, recursiveParentElement} from '../../../common/commonUtils.js';
+import {recursiveParentElement} from '../../../common/commonUtils.js';
import * as pb from '../../../workflows/proto/main_pb.js';
-import CRRunner from './actionRunners/replyWithCR.js';
+import AttributeRunner from './actionRunners/attribute.js';
import ReadStateRunner from './actionRunners/readState.js';
+import CRRunner from './actionRunners/replyWithCR.js';
import Thread from './models/thread.js';
export default class WorkflowRunner {
@@ -16,6 +17,7 @@
this._updateCallback = updateCallback;
// Initialize action runners:
+ this._AttributeRunner = new AttributeRunner();
this._CRRunner = new CRRunner();
this._ReadStateRunner = new ReadStateRunner();
}
@@ -55,6 +57,10 @@
_runAction() {
switch (this._currentAction?.getActionCase?.()) {
+ case pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION:
+ return this._AttributeRunner.execute(
+ this._currentAction?.getAttributeAction?.(), this._currentThread);
+
case pb.workflows.Action.ActionCase.REPLY_WITH_CR_ACTION:
return this._CRRunner.execute(
this._currentAction?.getReplyWithCrAction?.(), this._currentThread);
diff --git a/src/killSwitch/api_proto/kill_switch_grpc_web_pb.js b/src/killSwitch/api_proto/kill_switch_grpc_web_pb.js
index 47aeb29..b08346e 100644
--- a/src/killSwitch/api_proto/kill_switch_grpc_web_pb.js
+++ b/src/killSwitch/api_proto/kill_switch_grpc_web_pb.js
@@ -7,7 +7,7 @@
// Code generated by protoc-gen-grpc-web. DO NOT EDIT.
// versions:
// protoc-gen-grpc-web v1.4.2
-// protoc v3.21.12
+// protoc v4.24.4
// source: api_proto/kill_switch.proto
diff --git a/src/workflows/manager/components/ActionEditor.js b/src/workflows/manager/components/ActionEditor.js
index 9a57509..cdcb85a 100644
--- a/src/workflows/manager/components/ActionEditor.js
+++ b/src/workflows/manager/components/ActionEditor.js
@@ -1,5 +1,5 @@
+import './actions/Attribute.js';
import './actions/ReplyWithCR.js';
-
import '@material/mwc-circular-progress/mwc-circular-progress.js';
import {html, LitElement, nothing} from 'lit';
@@ -61,6 +61,14 @@
</wf-action-reply-with-cr>
`;
+ case pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION:
+ return html`
+ <wf-action-attribute
+ ?readOnly=${this.readOnly}
+ .action=${this.action.getAttributeAction()}>
+ </wf-action-attribute>
+ `;
+
case pb.workflows.Action.ActionCase.MARK_AS_READ_ACTION:
case pb.workflows.Action.ActionCase.MARK_AS_UNREAD_ACTION:
return nothing;
@@ -216,6 +224,9 @@
case pb.workflows.Action.ActionCase.REPLY_WITH_CR_ACTION:
return this.renderRoot.querySelector('wf-action-reply-with-cr');
+ case pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION:
+ return this.renderRoot.querySelector('wf-action-attribute');
+
default:
return null;
}
diff --git a/src/workflows/manager/components/actions/Attribute.js b/src/workflows/manager/components/actions/Attribute.js
new file mode 100644
index 0000000..150e7da
--- /dev/null
+++ b/src/workflows/manager/components/actions/Attribute.js
@@ -0,0 +1,89 @@
+import '@material/web/select/outlined-select.js';
+import '@material/web/select/select-option.js';
+
+import {html, LitElement} from 'lit';
+import {createRef, ref} from 'lit/directives/ref.js';
+
+import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
+import * as pb from '../../../proto/main_pb.js';
+
+import {FORM_STYLES} from './common.js';
+
+const kHiddenActions = [
+ pb.workflows.Action.AttributeAction.AttributeAction.AA_NONE,
+ pb.workflows.Action.AttributeAction.AttributeAction.AA_SOFT_LOCK,
+ pb.workflows.Action.AttributeAction.AttributeAction.AA_UNSOFT_LOCK,
+];
+
+export default class WFActionAttribute extends LitElement {
+ static properties = {
+ action: {type: Object},
+ readOnly: {type: Boolean},
+ };
+
+ static styles = [
+ SHARED_MD3_STYLES,
+ FORM_STYLES,
+ ];
+
+ attributeActionRef = createRef();
+
+ constructor() {
+ super();
+ this.action = new pb.workflows.Action.AttributeAction;
+ }
+
+ render() {
+ return html`
+ <div class="form-line">
+ <md-outlined-select ${ref(this.attributeActionRef)}
+ required
+ label="Action"
+ value=${this.action}
+ ?disabled=${this.readOnly}
+ @change=${this._attributeActionChanged}>
+ ${this.renderAttributeActions()}
+ </md-outlined-select>
+ </div>
+ `;
+ }
+
+ renderAttributeActions() {
+ const attributeActions =
+ Object.entries(pb.workflows.Action.AttributeAction.AttributeAction);
+ return attributeActions.filter(([, id]) => !kHiddenActions.includes(id))
+ .map(([actionCodename, id]) => html`
+ <md-select-option value=${id}>
+ <div slot="headline">${actionCodename}</div>
+ </md-select-option>
+ `);
+ }
+
+ checkValidity() {
+ return this.attributeActionRef.value.reportValidity();
+ }
+
+ _dispatchUpdateEvent() {
+ // Request an update for this component
+ this.requestUpdate();
+
+ // Transmit to other components that the action has changed
+ const e =
+ new Event('attribute-action-updated', {bubbles: true, composed: true});
+ this.renderRoot.dispatchEvent(e);
+ }
+
+ _attributeActionChanged() {
+ this.attributeAction = this.attributeActionRef.value.value;
+ }
+
+ get attributeAction() {
+ return this.action.getAttributeAction();
+ }
+
+ set attributeAction(value) {
+ this.action.setAttributeAction(value);
+ this._dispatchUpdateEvent();
+ }
+}
+window.customElements.define('wf-action-attribute', WFActionAttribute);
diff --git a/src/workflows/manager/components/actions/ReplyWithCR.js b/src/workflows/manager/components/actions/ReplyWithCR.js
index 40a6b9c..e97f6f4 100644
--- a/src/workflows/manager/components/actions/ReplyWithCR.js
+++ b/src/workflows/manager/components/actions/ReplyWithCR.js
@@ -8,6 +8,7 @@
import {SHARED_MD3_STYLES} from '../../../../common/styles/md3.js';
import * as pb from '../../../proto/main_pb.js';
+import { FORM_STYLES } from './common.js';
export default class WFActionReplyWithCR extends LitElement {
static properties = {
@@ -18,15 +19,8 @@
static styles = [
SHARED_MD3_STYLES,
+ FORM_STYLES,
css`
- .form-line {
- display: flex;
- flex-direction: row;
- align-items: center;
- margin-block: 1em;
- gap: .5rem;
- }
-
.select-cr-btn {
--md-outlined-button-icon-size: 24px;
}
diff --git a/src/workflows/manager/components/actions/common.js b/src/workflows/manager/components/actions/common.js
new file mode 100644
index 0000000..3d5be56
--- /dev/null
+++ b/src/workflows/manager/components/actions/common.js
@@ -0,0 +1,11 @@
+import {css} from 'lit';
+
+export const FORM_STYLES = css`
+ .form-line {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-block: 1em;
+ gap: .5rem;
+ }
+`;
diff --git a/src/workflows/manager/shared/actions.js b/src/workflows/manager/shared/actions.js
index fcf12e1..ef23926 100644
--- a/src/workflows/manager/shared/actions.js
+++ b/src/workflows/manager/shared/actions.js
@@ -1,5 +1,7 @@
import {css} from 'lit';
+import * as pb from '../../proto/main_pb.js';
+
// TODO: remove this and substitute it with proper localization.
export const kActionHeadings = {
0: 'Unknown action',
@@ -7,7 +9,7 @@
2: 'Move to a forum',
3: 'Mark as duplicate of a thread',
4: 'Unmark duplicate',
- 5: 'Change thread attributes',
+ 5: 'Change thread attribute',
6: 'Reply with canned response',
16: 'Star/unstar thread',
17: 'Subscribe/unsubscribe to thread',
@@ -17,7 +19,12 @@
21: 'Mark as unread',
};
-export const kSupportedActions = new Set([6, 20, 21]);
+export const kSupportedActions = new Set([
+ pb.workflows.Action.ActionCase.ATTRIBUTE_ACTION,
+ pb.workflows.Action.ActionCase.REPLY_WITH_CR_ACTION,
+ pb.workflows.Action.ActionCase.MARK_AS_READ_ACTION,
+ pb.workflows.Action.ActionCase.MARK_AS_UNREAD_ACTION,
+]);
export const kActionStyles = css`
.action {
diff --git a/src/workflows/proto/main.proto b/src/workflows/proto/main.proto
index 660be5e..dd10682 100644
--- a/src/workflows/proto/main.proto
+++ b/src/workflows/proto/main.proto
@@ -64,6 +64,11 @@
AA_UNSET_TRENDING = 9;
AA_SET_ISSUE_RESOLVED = 10;
AA_UNSET_ISSUE_RESOLVED = 11;
+ AA_SOFT_LOCK = 12;
+ AA_UNSOFT_LOCK = 13;
+ AA_EXCLUDE_FROM_GOLDEN = 14;
+ AA_UNEXCLUDE_FROM_GOLDEN = 15;
+ AA_INCLUDE_IN_GOLDEN = 16;
}
AttributeAction attribute_action = 1;
}
diff --git a/src/workflows/proto/main_pb.js b/src/workflows/proto/main_pb.js
index 961a61c..75ee13b 100644
--- a/src/workflows/proto/main_pb.js
+++ b/src/workflows/proto/main_pb.js
@@ -2195,7 +2195,12 @@
AA_SET_TRENDING: 8,
AA_UNSET_TRENDING: 9,
AA_SET_ISSUE_RESOLVED: 10,
- AA_UNSET_ISSUE_RESOLVED: 11
+ AA_UNSET_ISSUE_RESOLVED: 11,
+ AA_SOFT_LOCK: 12,
+ AA_UNSOFT_LOCK: 13,
+ AA_EXCLUDE_FROM_GOLDEN: 14,
+ AA_UNEXCLUDE_FROM_GOLDEN: 15,
+ AA_INCLUDE_IN_GOLDEN: 16
};
/**