Skip to content

Commit

Permalink
feat(NewPollForm): add schedule auto-close config input
Browse files Browse the repository at this point in the history
  • Loading branch information
dyaskur committed Sep 2, 2023
1 parent 62423ca commit 4ffd51d
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 52 deletions.
24 changes: 23 additions & 1 deletion src/cards/ClosePollFormCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,30 @@ export default class ClosePollFormCard extends BaseCard {
};
}


buildSections() {
this.card.sections = [];
this.card.sections = [
{
'widgets': [
{
'divider': {},
},
{
'divider': {},
},
{
'divider': {},
},
{
'divider': {},
},
{
'textParagraph': {
'text': 'If you have any problems, questions, or feedback, ' +
'please feel free to post them <a href="https://github.com/dyaskur/google-chat-poll/issues">here</a> ',
},
}],
}];
}

buildFooter() {
Expand Down
105 changes: 75 additions & 30 deletions src/cards/NewPollFormCard.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import BaseCard from './BaseCard';
import {ClosableType, PollConfig} from '../helpers/interfaces';
import {ClosableType, 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: chatV1.Schema$TimeZone;

constructor(config: PollConfig) {
constructor(config: PollForm, timezone: chatV1.Schema$TimeZone | undefined = undefined) {
super();
this.config = config;
if (timezone && timezone.offset) {
this.timezone = timezone;
} else {
this.timezone = {offset: 0, id: 'GMT'};
}
}

create() {
Expand All @@ -20,14 +27,15 @@ export default class NewPollFormCard extends BaseCard {
buildSections() {
this.buildTopicInputSection();
this.buildOptionSwitchSection();
this.buildCloseConfigSection();
}

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 +56,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,37 +69,74 @@ 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',
},
],
}];
if (this.config.autoclose) {
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(),
},
});
}
this.card.sections!.push({
widgets,
});
}

Expand Down
13 changes: 11 additions & 2 deletions src/handlers/ActionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export default class ActionHandler extends BaseHandler implements PollAction {
case 'add_option':
return await this.saveOption();
case 'show_form':
return createDialogActionResponse(new NewPollFormCard({topic: '', choices: []}).create());
const pollForm = new NewPollFormCard({topic: '', choices: []}, this.getUserTimezone()).create();
return createDialogActionResponse(pollForm);
case 'new_poll_on_change':
return this.newPollOnChange();
case 'close_poll_form':
return this.closePollForm();
case 'close_poll':
Expand All @@ -57,7 +60,7 @@ export default class ActionHandler extends BaseHandler implements PollAction {

if (!config.topic || config.choices.length === 0) {
// Incomplete form submitted, rerender
const dialog = new NewPollFormCard(config).create();
const dialog = new NewPollFormCard(config, this.getUserTimezone()).create();
return createDialogActionResponse(dialog);
}
const pollCard = new PollCard({
Expand Down Expand Up @@ -186,4 +189,10 @@ export default class ActionHandler extends BaseHandler implements PollAction {
};
return createDialogActionResponse(new MessageDialogCard(dialogConfig).create());
}

newPollOnChange() {
const formValues: PollFormInputs = this.event.common!.formInputs! as PollFormInputs;
const config = getConfigFromInput(formValues);
return createDialogActionResponse(new NewPollFormCard(config, this.getUserTimezone()).create());
}
}
4 changes: 4 additions & 0 deletions src/handlers/BaseHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ export default abstract class BaseHandler {
return this.getAnnotations().find((annotation) => annotation.type === type);
}

protected getUserTimezone(): chatV1.Schema$TimeZone | undefined {
return this.event.common?.timeZone;
}

public abstract process(): chatV1.Schema$Message | Promise<chatV1.Schema$Message>;
}
4 changes: 2 additions & 2 deletions src/handlers/CommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ export default class CommandHandler extends BaseHandler {
type: 'DIALOG',
dialogAction: {
dialog: {
body: new NewPollFormCard(options).create(),
body: new NewPollFormCard(options, this.getUserTimezone()).create(),
},
},
},
};
default:
const isPrivate = this.event!.space?.type === 'DM';
const isPrivate = this.event.space?.type === 'DM';
return {
thread: this.event.message!.thread,
actionResponse: {
Expand Down
9 changes: 9 additions & 0 deletions src/helpers/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export interface PollConfig {
type?: ClosableType,
}

export interface PollForm extends PollConfig{
autoclose?: boolean,
timezone_offset?: number | null,
timezone_name?: string | null,
close_schedule_time?: number
}

export interface PollState extends PollConfig{
choiceCreator?: ChoiceCreator,
author?: chatV1.Schema$User,
Expand All @@ -39,6 +46,8 @@ export interface PollFormInputs {
is_anonymous: chatV1.Schema$Inputs,
allow_add_option: chatV1.Schema$Inputs,
type: chatV1.Schema$Inputs,
timezone: chatV1.Schema$Inputs,
close_schedule_time: chatV1.Schema$Inputs,
[key: string]: chatV1.Schema$Inputs, // for unknown number option
}

Expand Down
16 changes: 11 additions & 5 deletions src/helpers/state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ClosableType, PollConfig, PollFormInputs, PollState} from './interfaces';
import {ClosableType, PollForm, PollFormInputs, PollState} from './interfaces';
import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
import {MAX_NUM_OF_OPTIONS} from '../config/default';

Expand All @@ -22,6 +22,9 @@ export function addOptionToState(option: string, state: PollState, creator = '')

export function getStateFromCard(event: chatV1.Schema$DeprecatedEvent) {
const card = event.message?.cardsV2?.[0]?.card as chatV1.Schema$GoogleAppsCardV1Card;
if (!card) {
throw new ReferenceError('no valid card in the event');
}
return getStateFromCardName(card) || getStateFromParameter(event) || getStateFromCardWhenNoHeader(card) ||
getStateFromCardWhenHasHeader(card);
}
Expand All @@ -38,28 +41,31 @@ function getChoicesFromInput(formValues: PollFormInputs) {
}

export function getConfigFromInput(formValues: PollFormInputs) {
const state: PollConfig = {topic: '', choices: []};
const state: PollForm = {topic: '', choices: []};
state.topic = formValues.topic.stringInputs!.value![0]!.trim() ?? '';
state.anon = formValues.is_anonymous?.stringInputs!.value![0] === '1';
state.optionable = formValues.allow_add_option?.stringInputs!.value![0] === '1';
state.type = parseInt(formValues.type?.stringInputs!.value![0] ?? '1') as ClosableType;
state.autoclose = formValues.is_autoclose?.stringInputs!.value![0] === '1';
state.close_schedule_time = parseInt(formValues.close_schedule_time?.dateTimeInput!.msSinceEpoch ?? '1');
state.choices = getChoicesFromInput(formValues);

console.log(JSON.stringify(state));
return state;
}

function getStateFromCardWhenNoHeader(card: chatV1.Schema$GoogleAppsCardV1Card) {
return card?.sections?.[0].widgets?.[0].decoratedText?.button?.onClick?.action?.parameters?.[0]?.value;
return card.sections?.[0].widgets?.[0].decoratedText?.button?.onClick?.action?.parameters?.[0]?.value;
}

function getStateFromCardWhenHasHeader(card: chatV1.Schema$GoogleAppsCardV1Card) {
// when has header the first section is header
return card?.sections?.[1].widgets?.[0].decoratedText?.button?.onClick?.action?.parameters?.[0]?.value;
return card.sections?.[1].widgets?.[0].decoratedText?.button?.onClick?.action?.parameters?.[0]?.value;
}

function getStateFromCardName(card: chatV1.Schema$GoogleAppsCardV1Card) {
// when has header the first section is header
return card?.name;
return card.name;
}

function getStateFromParameter(event: chatV1.Schema$DeprecatedEvent) {
Expand Down
66 changes: 66 additions & 0 deletions src/helpers/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// export function msToTime(valueMsEpoch: number) {
// const seconds = Math.floor((valueMsEpoch / 1000) % 60);
// const minutes = Math.floor((valueMsEpoch / (1000 * 60)) % 60);
// const hours = Math.floor((valueMsEpoch / (1000 * 60 * 60)) % 24);
//
// return hours + ':' + minutes + ':' + seconds;
// }
//
// export function msToClock(valueMsEpoch: number) {
// const minutes = Math.floor((valueMsEpoch / (1000 * 60)) % 60);
// const hours = Math.floor((valueMsEpoch / (1000 * 60 * 60)) % 24);
//
// return hours + ':' + minutes;
// }

function getTimezoneList(): { [key: number]: string } {
const timezoneTable: { [key: number]: string } = {
'-43200000': '(GMT -12:00) Eniwetok, Kwajalein',
'-39600000': '(GMT -11:00) Midway Island, Samoa',
'-36000000': '(GMT -10:00) Hawaii',
'-32400000': '(GMT -9:00) Alaska',
'-28800000': '(GMT -8:00) Pacific Time',
'-25200000': '(GMT -7:00) Mountain Time',
'-21600000': '(GMT -6:00) Central Time, Mexico City',
'-18000000': '(GMT -5:00) Eastern Time, Bogota, Lima',
'-14400000': '(GMT -4:00) Atlantic Time, Caracas, La Paz',
'-12600000': '(GMT -3:30) Newfoundland',
'-10800000': '(GMT -3:00) Brazil, Buenos Aires, Georgetown',
'-7200000': '(GMT -2:00) Mid-Atlantic',
'-3600000': '(GMT -1:00) Azores, Cape Verde Islands',
'0': '(GMT) Western Europe Time, London',
'3600000': '(GMT +1:00) Brussels, Copenhagen, Madrid, Paris',
'7200000': '(GMT +2:00) Kaliningrad, South Africa',
'10800000': '(GMT +3:00) Baghdad, Riyadh, Moscow',
'12600000': '(GMT +3:30) Tehran',
'14400000': '(GMT +4:00) Abu Dhabi, Muscat, Baku, Tbilisi',
'16200000': '(GMT +4:30) Kabul',
'18000000': '(GMT +5:00) Islamabad, Karachi, Tashkent',
'19800000': '(GMT +5:30) Bombay, Calcutta, Madras, New Delhi',
'21600000': '(GMT +6:00) Almaty, Dhaka, Colombo',
'25200000': '(GMT +7:00) Bangkok, Hanoi, Jakarta',
'28800000': '(GMT +8:00) Beijing, Perth, Singapore, Hong Kong',
'32400000': '(GMT +9:00) Tokyo, Seoul, Osaka, Sapporo, Yakutsk',
'34200000': '(GMT +9:30) Adelaide, Darwin',
'36000000': '(GMT +10:00) Eastern Australia, Guam, Vladivostok',
'39600000': '(GMT +11:00) Magadan, Solomon Islands',
'43200000': '(GMT +12:00) Auckland, Wellington, Fiji',
};
return timezoneTable;
}

// export function timezoneDropdown(selectedOffset: string): { text: string, value: string, selected: boolean }[] {
// const timezones = getTimezoneList();
// // foreach timezone
// const formattedTimezones = Object.keys(timezones).map((offset) => ({
// text: timezones[offset],
// value: offset,
// selected: offset === selectedOffset,
// }));
// return formattedTimezones;
// }

export function offsetToTimezone(offset: number) {
const timezones = getTimezoneList();
return timezones[offset];
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {HttpFunction} from '@google-cloud/functions-framework/build/src/functions';


import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
import CommandHandler from './handlers/CommandHandler';
import MessageHandler from './handlers/MessageHandler';
Expand Down Expand Up @@ -58,6 +57,7 @@ export const app: HttpFunction = async (req, res) => {
console.log(event.type,
event.common?.invokedFunction || event.message?.slashCommand?.commandId || event.message?.argumentText,
event.user.displayName, event.user.email, event.space.type, event.space.name);
console.log(JSON.stringify(event));

let reply: chatV1.Schema$Message = {
thread: event.message?.thread,
Expand Down
Loading

0 comments on commit 4ffd51d

Please sign in to comment.