Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add asyncapi events scheduling automation #308

Merged
merged 13 commits into from
Apr 20, 2022
33 changes: 33 additions & 0 deletions .github/workflows/cancel-event.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Cancel event when issue was closed before it took place

on:
issues:
types:
- closed

jobs:

cancel_event:
env:
CALENDAR_ID: ${{ secrets.CALENDAR_ID }}
CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }}
name: Remove event from calendar
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 16
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install deps
run: npm install
working-directory: ./.github/workflows/create-event-helpers
- name: Remove Google Calendar entry
uses: actions/github-script@v4
with:
script: |
const { deleteEvent } = require('./.github/workflows/create-event-helpers/calendar/index.js');
deleteEvent('${{ github.event.issue.number }}', '${{ github.event.issue.closed_at }}');
39 changes: 39 additions & 0 deletions .github/workflows/create-event-ad-hoc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Schedule Ad Hoc Meeting

on:
workflow_dispatch:
inputs:
time:
description: 'Info about meeting hour in UTC time zone, like: 08 or 16. No PM or AM versions.'
required: true
date:
description: 'Date in a form like: 2022-04-05 where 04 is a month and 05 is a day number.'
required: true
name:
description: 'Provide short title for the meeting.'
required: true
desc:
description: 'Provide description that explains the purpose of the meeting'
required: true

jobs:

setup-ad-hoc:
uses: ./.github/workflows/create-event-workflow-reusable.yml
with:
time: ${{ github.event.inputs.time }}
date: ${{ github.event.inputs.date }}
meeting_name: ${{ github.event.inputs.name }}
meeting_desc: ${{ github.event.inputs.desc }}
host: lpgornicki@gmail.com
alternative_host: fmvilas@gmail.com
issue_template_path: .github/workflows/create-event-helpers/issues_templates/ad-hoc.md
create_zoom: true
secrets:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
ZOOM_API_KEY: ${{ secrets.ZOOM_API_KEY }}
ZOOM_API_SECRET: ${{ secrets.ZOOM_API_SECRET }}
STREAM_URL: ${{ secrets.STREAM_URL }}
STREAM_KEY: ${{ secrets.STREAM_KEY }}
CALENDAR_ID: ${{ secrets.CALENDAR_ID }}
CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }}
33 changes: 33 additions & 0 deletions .github/workflows/create-event-community-meeting.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Schedule Community Meeting

on:
workflow_dispatch:
inputs:
time:
description: 'Info about meeting hour in UTC time zone, like: 08 or 16. No PM or AM versions.'
required: true
date:
description: 'Date in a form like: 2022-04-05 where 04 is a month and 05 is a day number.'
required: true

jobs:

setup-community-meeting:
uses: ./.github/workflows/create-event-workflow-reusable.yml
with:
time: ${{ github.event.inputs.time }}
date: ${{ github.event.inputs.date }}
meeting_name: Community Meeting
meeting_desc: This is a community meeting to regularly talk in open about important topics around AsyncAPI Initiative.
host: lpgornicki@gmail.com
alternative_host: fmvilas@gmail.com
issue_template_path: .github/workflows/create-event-helpers/issues_templates/community.md
create_zoom: true
secrets:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
ZOOM_API_KEY: ${{ secrets.ZOOM_API_KEY }}
ZOOM_API_SECRET: ${{ secrets.ZOOM_API_SECRET }}
STREAM_URL: ${{ secrets.STREAM_URL }}
STREAM_KEY: ${{ secrets.STREAM_KEY }}
CALENDAR_ID: ${{ secrets.CALENDAR_ID }}
CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }}
155 changes: 155 additions & 0 deletions .github/workflows/create-event-helpers/calendar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
const { google } = require('googleapis')
const core = require('@actions/core');

module.exports = { addEvent, deleteEvent, listEvents }

const auth = new google.auth.GoogleAuth({
scopes: ['https://www.googleapis.com/auth/calendar'],
credentials: JSON.parse(process.env.CALENDAR_SERVICE_ACCOUNT)
});

const calendar = google.calendar({ version: 'v3', auth });

/**
* Adds new single-occuring event
* All events are being linked to their GitHub issues
* @param {String} zoomUrl Zoom url of the meeting
* @param {String} startDate ex. 2022-04-05
* @param {String} startTime ex. 08 or 16
* @param {Number} issueNumber GitHub issue number of the event, to find event later
*/
async function addEvent(zoomUrl, startDate, startTime, issueNumber) {

const communityIssuesUrl = 'https://github.com/asyncapi/community/issues/';
const title = process.env.MEETING_NAME;
const suffix = process.env.MEETING_NAME_SUFFIX;
const description = process.env.MEETING_DESC;
const guest = process.env.GUEST;
const summary = suffix ? `${title} ${suffix}` : title;

try {

//helper to create end time which is always 1h later
const getEndTime = (startTime) => {
const time = Number(startTime);
if (time < 10) return '0' + (time + 1)

return (time + 1) + ''
}

//helper to build meeting description
//there is a use case that meeting has no connection over zoom available as it is pure live stream
const getDescription = (description, communityIssuesUrl, issueNumber, zoomUrl, guest) => {

const zoomDetails = zoomUrl && `<b>Zoom</b>: <a href="${zoomUrl}">Meeting Link</a>`;
const agendaDetails = `<b>Agenda and more options to join the meeting</b>: <a href="${communityIssuesUrl}${issueNumber}">GitHub Issue Link.</a>`;
const guestDetails = guest ? `<b>Special guest</b>: ${ guest }` : '';
return `${ description }<br><br>${ zoomDetails }<br><br>${ agendaDetails }<br><br>${ guestDetails }`
};

await calendar.events.insert({
calendarId: process.env.CALENDAR_ID,
requestBody: {
summary,
description: getDescription(description, communityIssuesUrl, issueNumber, zoomUrl, guest),
start: {
dateTime: `${ startDate }T${ startTime }:00:00Z`
},
end: {
dateTime: `${ startDate }T${ getEndTime(startTime) }:00:00Z`
},
location: zoomUrl,
extendedProperties: {
private: {
'ISSUE_ID': `${issueNumber}`
}
}
}
})

core.info('Event created')
} catch (error) {
core.setFailed(`Faild creating event in Google Calendar: ${ JSON.stringify(error) }`)
}
}

/**
* Deletes a single-occuring event from issue number
* @param {Number} issueNumber GitHub issue number of the meeting to delete
* @param {Number} closeDate Date when issue was closed
*/
async function deleteEvent(issueNumber, closeDate) {
let events

try {
events = (await calendar.events.list({
calendarId: process.env.CALENDAR_ID,
privateExtendedProperty: `ISSUE_ID=${issueNumber}`
})).data;
} catch (error) {
return core.setFailed(`Failed to fetch events for issue numer ${ issueNumber }: ${ JSON.stringify(error) }`)
}

const eventsItems = events.items;

if (eventsItems.length > 0) {

const meetingId = eventsItems[0].id;
const eventStartDate = new Date(eventsItems[0].start.dateTime);
const issueCloseDate = new Date(closeDate);

try {
//in case of issue was closed after the event, we do not remove it from calendar
if (eventStartDate.getTime() < issueCloseDate.getTime()) return core.info('Event not removed as related issue was closed after the event took place.');

await calendar.events.delete({
calendarId: process.env.CALENDAR_ID,
eventId: meetingId
})

core.info('Event deleted from calendar')
} catch (error) {
core.setFailed(`Failed to delete event for issue number ${ issueNumber }: ${ JSON.stringify(error) }`)
}
} else {
core.info('Event not found in calendar')
}
}

/**
* Lists all events including single-occuring and recurring
*/
async function listEvents() {

let eventsItems;

try {
//this runs always on friday midnight
const currentTime = new Date(Date.now()).toISOString();
//we check moday
const timeIn2Days = new Date(Date.parse(currentTime) + 2 * 24 * 60 * 60 * 1000).toISOString();
//till friday
const timeIn8Days = new Date(Date.parse(currentTime) + 8 * 24 * 60 * 60 * 1000).toISOString();

const eventsList = await calendar.events.list({
calendarId: process.env.CALENDAR_ID,
timeMax: timeIn8Days,
timeMin: timeIn2Days
})

eventsItems = eventsList.data.items.map((e) => {
return {
title: e.summary,
issueId: e.extendedProperties.private.ISSUE_ID,
date: new Date(e.start.dateTime).toUTCString()
}
})

core.info(`List of all events: ${ JSON.stringify(eventsList.data, null, 4) }`)
} catch (error) {
return core.setFailed(`Faild fetching events from Google Calendar API: ${ JSON.stringify(error) }`)
}

return eventsItems;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
title: "{{ env.MEETING_NAME }}, {{ env.FULL_DATE | date('H:mm [UTC] dddd MMMM Do YYYY') }}"
---

<table>
<tr>
<th>Meeting Info</th>
<th>Details</th>
</tr>
<tr>
<td>Purpose</td>
<td>{{ env.MEETING_DESC }}</td>
</tr>
<tr>
<td>Time</td>
<td><strong>{{ env.FULL_DATE | date('H') }}:00 UTC</strong> | Translate to your time zone with <a href="https://dateful.com/convert/coordinated-universal-time-utc?t={{ env.FULL_DATE | date('kk') }}&d={{ env.DATE_ONLY }}">time zone converter</a>.</td>
derberg marked this conversation as resolved.
Show resolved Hide resolved
</tr>
<tr>
<th>Meeting Place</th>
<th>Link</th>
</tr>
<tr>
<td>Zoom</td>
<td><a href="{{ env.ZOOM_LINK }}">Join live</a>.</td>
</tr>
<tr>
<td>YouTube</td>
<td><a href="https://www.youtube.com/asyncapi">Watch live and interact through live chat</a>.</td>
</tr>
<tr>
<td>Twitch</td>
<td><a href="https://www.twitch.tv/asyncapi">Watch live</a>.</td>
</tr>
<tr>
<td>Twitter</td>
<td><a href="https://twitter.com/AsyncAPISpec">Watch live</a>.</td>
</tr>
<tr>
<td>LinkedIn</td>
<td><a href="https://www.linkedin.com/company/asyncapi">Watch live</a>.</td>
</tr>
<tr>
<th>More Info</th>
<th>Details</th>
</tr>
<tr>
<td>Meeting Recordings</td>
<td><a href="https://www.youtube.com/asyncapi">YouTube Playlist</a>.</td>
</tr>
<tr>
<td>AsyncAPI Initiative Calendar</td>
<td><a href="https://calendar.google.com/calendar/embed?src=c_q9tseiglomdsj6njuhvbpts11c%40group.calendar.google.com&ctz=UTC">Public URL</a>.</td>
</tr>
<tr>
<td>iCal File</td>
<td><a href="https://calendar.google.com/calendar/ical/c_q9tseiglomdsj6njuhvbpts11c%40group.calendar.google.com/public/basic.ics">Add to your calendar</a>.</td>
</tr>
<tr>
<td>Newsletter</td>
<td><a href="https://www.asyncapi.com/newsletter">Subscribe to get weekly updates on upcoming meetings</a>.</td>
</tr>
</table>


## Agenda

> Don't wait for the meeting to discuss topics that already have issues. Feel free to comment on them earlier.

1. Q&A
1. _Place for your topic_
1. Q&A

## Notes

tbd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Notes
tbd

Seems odd, like a mistake to have the tbd left in there..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is intentional.
once issue is created, there are no notes, thus tbd - to be done/delivered. How can we do it differently? more details sentence like "Notes are provided after the meeting?"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you feel about writing instead: Add notes here after the meeting.

(Can they add notes later, or did I completely misunderstand how it works? 😆 )

@derberg

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you understood it right, we never write them real-time but always after meeting, a kind of summary. I updated PR with your suggestion, thanks!!!

Loading