Skip to content

Commit

Permalink
Merge pull request #20 from dyaskur/9-add-the-ability-to-auto-close
Browse files Browse the repository at this point in the history
feat: add close schedule feature to automatic close poll at specific time
  • Loading branch information
dyaskur authored Sep 8, 2023
2 parents 605e3df + b6f9471 commit 3de0bb1
Show file tree
Hide file tree
Showing 36 changed files with 2,476 additions and 1,163 deletions.
3 changes: 3 additions & 0 deletions .env.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FUNCTION_REGION: asia-southeast1
GCP_PROJECT: yaskur-project
QUEUE_NAME: yaskur-queue
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
.idea
dist/*
yarn-error.log
.env.yaml
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ module.exports = {
transform: {
'^.+\\.ts?$': 'babel-jest',
},
clearMocks: true,
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"build": "tsc",
"start": "functions-framework --target=app",
"prestart": "yarn build",
"deploy": "gcloud functions deploy app --env-vars-file .env.yaml --trigger-http --security-level=secure-always --allow-unauthenticated --runtime nodejs18",
"gcp-build": "yarn build"
},
"keywords": [],
Expand All @@ -18,6 +19,7 @@
"@babel/preset-env": "^7.22.9",
"@babel/preset-typescript": "^7.22.5",
"@google-cloud/functions-framework": "^3.3.0",
"@google-cloud/tasks": "^4.0.0",
"babel-jest": "^29.6.2",
"googleapis": "123.0.0"
},
Expand Down
23 changes: 22 additions & 1 deletion src/cards/BaseCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,38 @@ interface Card {
}

export default abstract class BaseCard implements Card {
private id: string = 'cardId';
protected id: string = 'cardId';
private _content: chatV1.Schema$GoogleAppsCardV1Section[] = [];

protected card: chatV1.Schema$GoogleAppsCardV1Card = {
sections: this._content,
};

protected createButton(
text: string, action: string, interaction: string | undefined = undefined,
parameters = []): chatV1.Schema$GoogleAppsCardV1Button {
const button: chatV1.Schema$GoogleAppsCardV1Button = {
text,
'onClick': {
'action': {
'function': action,
},
},
};
interaction && (button.onClick!.action!.interaction = interaction);
parameters.length && (button.onClick!.action!.parameters = parameters);
return button;
}

protected addSectionWidget(widget: chatV1.Schema$GoogleAppsCardV1Widget) {
this.card.sections!.push({widgets: [widget]});
}

abstract buildSections(): void;

abstract create(): chatV1.Schema$GoogleAppsCardV1Card;


createCardWithId(): chatV1.Schema$CardWithId {
return {
'cardId': this.id,
Expand Down
64 changes: 50 additions & 14 deletions src/cards/ClosePollFormCard.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import BaseCard from './BaseCard';
import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
import {LocaleTimezone, PollState} from '../helpers/interfaces';
import {generateHelperWidget} from '../helpers/helper';

export default class ClosePollFormCard extends BaseCard {
id = 'close_poll_form';
state: PollState;
timezone: LocaleTimezone;

constructor(config: PollState, timezone: LocaleTimezone) {
super();
this.state = config;
this.timezone = timezone;
}

create(): chatV1.Schema$GoogleAppsCardV1Card {
this.buildHeader();
if (this.state.closedTime) {
this.buildCurrentScheduleInfo();
}
this.buildButtons();
this.buildSections();
this.buildFooter();
return this.card;
}

Expand All @@ -18,21 +33,42 @@ export default class ClosePollFormCard extends BaseCard {
};
}

buildSections() {
this.card.sections = [];
buildCurrentScheduleInfo() {
const locale = this.timezone.locale;
const closedDate = new Date(this.state.closedTime!).toLocaleString(locale, {timeZone: this.timezone.id});
this.card.sections!.push(
{
'widgets': [
{
'decoratedText': {
'text': `<i>This poll already has Auto Close schedule at <time> ${closedDate}</time>(${this.timezone.id}) </i>`,
'startIcon': {
'knownIcon': 'CLOCK',
'altText': '@',
},
},
},
],
});
}

buildFooter() {
this.card.fixedFooter = {
'primaryButton': {
'text': 'Close Poll',
'onClick': {
'action': {
'function': 'close_poll',
'parameters': [],
},
},
buildButtons() {
let scheduleButtonText = 'Create Schedule Close';
if (this.state.closedTime) {
scheduleButtonText = 'Edit Schedule Close';
}

this.addSectionWidget({
'buttonList': {
'buttons': [
this.createButton(scheduleButtonText, 'schedule_close_poll_form'),
this.createButton('Close Now', 'close_poll'),
],
},
};
});
}

buildSections() {
this.card.sections!.push(generateHelperWidget());
}
}
135 changes: 96 additions & 39 deletions src/cards/NewPollFormCard.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import BaseCard from './BaseCard';
import {ClosableType, PollConfig} from '../helpers/interfaces';
import {ClosableType, LocaleTimezone, PollForm} from '../helpers/interfaces';
import {MAX_NUM_OF_OPTIONS} from '../config/default';
import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
import {offsetToTimezone} from '../helpers/time';

export default class NewPollFormCard extends BaseCard {
private config: PollConfig;
private config: PollForm;
private timezone: LocaleTimezone;

constructor(config: PollConfig) {
constructor(config: PollForm, timezone: LocaleTimezone) {
super();
this.config = config;
this.timezone = timezone;
}

create() {
Expand All @@ -20,14 +23,18 @@ export default class NewPollFormCard extends BaseCard {
buildSections() {
this.buildTopicInputSection();
this.buildOptionSwitchSection();
this.buildCloseConfigSection();
if (this.config.autoClose) {
this.buildAutoCloseSection();
}
}

buildTopicInputSection() {
const widgets = [];
widgets.push(this.buildHelpText());
widgets.push(this.topicInput(this.config.topic));
for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
const choice = this.config?.choices?.[i];
const choice = this.config.choices?.[i];
widgets.push(this.optionInput(i, choice));
}
this.card.sections!.push({
Expand All @@ -48,7 +55,7 @@ export default class NewPollFormCard extends BaseCard {
'controlType': 'SWITCH',
'name': 'is_anonymous',
'value': '1',
'selected': this.config?.anon ?? false,
'selected': this.config.anon ?? false,
},
},
'horizontalAlignment': 'CENTER',
Expand All @@ -61,44 +68,101 @@ export default class NewPollFormCard extends BaseCard {
'controlType': 'SWITCH',
'name': 'allow_add_option',
'value': '1',
'selected': this.config?.optionable ?? true,
'selected': this.config.optionable ?? true,
},
},
'horizontalAlignment': 'CENTER',
},
{
'selectionInput': {
'type': 'DROPDOWN',
'label': 'Allow to close poll',
'name': 'type',
'items': [
{
'text': 'Yes, but only creator',
'value': '1',
'selected': this.config.type === ClosableType.CLOSEABLE_BY_CREATOR,
},
{
'text': 'Yes, anyone can close',
'value': '2',
'selected': this.config.type === ClosableType.CLOSEABLE_BY_ANYONE,
},
{
'text': 'No, I want unclosable poll',
'value': '0',
'selected': this.config.type === ClosableType.UNCLOSEABLE,
},
],
],
});
}

buildCloseConfigSection() {
const widgets: chatV1.Schema$GoogleAppsCardV1Widget[] = [
{
'selectionInput': {
'type': 'DROPDOWN',
'label': 'Allow to manually close poll',
'name': 'type',
'items': [
{
'text': 'Yes, but only creator',
'value': '1',
'selected': this.config.type === ClosableType.CLOSEABLE_BY_CREATOR,
},
{
'text': 'Yes, anyone can close',
'value': '2',
'selected': this.config.type === ClosableType.CLOSEABLE_BY_ANYONE,
},
{
'text': 'No, I want unclosable poll',
'value': '0',
'selected': this.config.type === ClosableType.UNCLOSEABLE,
},
],
},
'horizontalAlignment': 'START',
},
{
'decoratedText': {
'topLabel': '',
'text': 'Automatic close poll at certain time',
'bottomLabel': 'The schedule time will show up',
'switchControl': {
'controlType': 'SWITCH',
'name': 'is_autoclose',
'value': '1',
'selected': this.config.autoClose ?? false,
'onChangeAction': {
'function': 'new_poll_on_change',
'parameters': [],
},
},
'horizontalAlignment': 'START',
},
],
}];
this.card.sections!.push({
widgets,
});
}

buildAutoCloseSection() {
const widgets: chatV1.Schema$GoogleAppsCardV1Widget[] = [];
const timezone = offsetToTimezone(this.timezone.offset);
const nowMs = Date.now() + this.timezone.offset + 18000000;
widgets.push(
{
'dateTimePicker': {
'label': 'Close schedule time ' + timezone,
'name': 'close_schedule_time',
'type': 'DATE_AND_TIME',
'valueMsEpoch': nowMs.toString(),
},
});

widgets.push(
{
'decoratedText': {
'text': 'Auto mention <b>@all</b> on 5 minutes before poll closed',
'bottomLabel': 'This is to prevent other users to vote before the poll is closed',
'switchControl': {
'controlType': 'SWITCH',
'name': 'auto_mention',
'value': '1',
'selected': this.config.autoMention ?? false,
},
},
});
this.card.sections!.push({
widgets,
});
}

buildHelpText() {
return {
textParagraph: {
text: 'Enter the poll topic and up to 10 choices in the poll. Blank options will be omitted.',
text: 'Enter the poll topic and up to 10 choices in the poll. Blank options will be omitted.<br>' +
'For scheduled auto close, the minimum time is 5 minutes after poll created.',
},
};
}
Expand Down Expand Up @@ -128,14 +192,7 @@ export default class NewPollFormCard extends BaseCard {

buildFooter() {
this.card.fixedFooter = {
'primaryButton': {
'text': 'Submit',
'onClick': {
'action': {
'function': 'start_poll',
},
},
},
'primaryButton': this.createButton('Submit', 'start_poll'),
};
}
}
Loading

0 comments on commit 3de0bb1

Please sign in to comment.