Commit 14f27102 authored by Simon Knox's avatar Simon Knox

Add Join meeting button to Issues with Zoom links

Detect links containing zoom.us followed by j, s, or my
Add link below Issue title that links to Zoom meeting
parent 57783259
...@@ -11,6 +11,7 @@ import titleComponent from './title.vue'; ...@@ -11,6 +11,7 @@ import titleComponent from './title.vue';
import descriptionComponent from './description.vue'; import descriptionComponent from './description.vue';
import editedComponent from './edited.vue'; import editedComponent from './edited.vue';
import formComponent from './form.vue'; import formComponent from './form.vue';
import PinnedLinks from './pinned_links.vue';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor'; import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
export default { export default {
...@@ -19,6 +20,7 @@ export default { ...@@ -19,6 +20,7 @@ export default {
titleComponent, titleComponent,
editedComponent, editedComponent,
formComponent, formComponent,
PinnedLinks,
}, },
mixins: [recaptchaModalImplementor], mixins: [recaptchaModalImplementor],
props: { props: {
...@@ -340,6 +342,7 @@ export default { ...@@ -340,6 +342,7 @@ export default {
:title-text="state.titleText" :title-text="state.titleText"
:show-inline-edit-button="showInlineEditButton" :show-inline-edit-button="showInlineEditButton"
/> />
<pinned-links :description-html="state.descriptionHtml" />
<description-component <description-component
v-if="state.descriptionHtml" v-if="state.descriptionHtml"
:can-update="canUpdate" :can-update="canUpdate"
......
<script>
import { GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
Icon,
GlLink,
},
props: {
descriptionHtml: {
type: String,
required: true,
},
},
computed: {
linksInDescription() {
const el = document.createElement('div');
el.innerHTML = this.descriptionHtml;
return [...el.querySelectorAll('a')].map(a => a.href);
},
// Detect links matching the following formats:
// Zoom Start links: https://zoom.us/s/<meeting-id>
// Zoom Join links: https://zoom.us/j/<meeting-id>
// Personal Zoom links: https://zoom.us/my/<meeting-id>
// Vanity Zoom links: https://gitlab.zoom.us/j/<meeting-id> (also /s and /my)
zoomHref() {
const zoomRegex = /^https:\/\/([\w\d-]+\.)?zoom\.us\/(s|j|my)\/.+/;
return this.linksInDescription.reduce((acc, currentLink) => {
let lastLink = acc;
if (zoomRegex.test(currentLink)) {
lastLink = currentLink;
}
return lastLink;
}, '');
},
},
};
</script>
<template>
<div v-if="zoomHref" class="border-bottom mb-3 mt-n2">
<gl-link
:href="zoomHref"
target="_blank"
class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
>
<icon name="brand-zoom" :size="14" />
<strong class="vertical-align-top">{{ __('Join Zoom meeting') }}</strong>
</gl-link>
</div>
</template>
...@@ -416,6 +416,7 @@ img.emoji { ...@@ -416,6 +416,7 @@ img.emoji {
.center { text-align: center; } .center { text-align: center; }
.block { display: block; } .block { display: block; }
.flex { display: flex; } .flex { display: flex; }
.vertical-align-top { vertical-align: top; }
.vertical-align-middle { vertical-align: middle; } .vertical-align-middle { vertical-align: middle; }
.vertical-align-sub { vertical-align: sub; } .vertical-align-sub { vertical-align: sub; }
.flex-align-self-center { align-self: center; } .flex-align-self-center { align-self: center; }
......
---
title: Add Join meeting button to issues with Zoom links
merge_request: 29454
author:
type: added
...@@ -5594,6 +5594,9 @@ msgstr "" ...@@ -5594,6 +5594,9 @@ msgstr ""
msgid "Job|with" msgid "Job|with"
msgstr "" msgstr ""
msgid "Join Zoom meeting"
msgstr ""
msgid "Jul" msgid "Jul"
msgstr "" msgstr ""
......
...@@ -92,6 +92,19 @@ describe "User creates issue" do ...@@ -92,6 +92,19 @@ describe "User creates issue" do
.and have_content(label_titles.first) .and have_content(label_titles.first)
end end
end end
context "with Zoom link" do
it "adds Zoom button" do
issue_title = "Issue containing Zoom meeting link"
zoom_url = "https://gitlab.zoom.us/j/123456789"
fill_in("Title", with: issue_title)
fill_in("Description", with: zoom_url)
click_button("Submit issue")
expect(page).to have_link('Join Zoom meeting', href: zoom_url)
end
end
end end
context "when signed in as user with special characters in their name" do context "when signed in as user with special characters in their name" do
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import PinnedLinks from '~/issue_show/components/pinned_links.vue';
const localVue = createLocalVue();
const plainZoomUrl = 'https://zoom.us/j/123456789';
const vanityZoomUrl = 'https://gitlab.zoom.us/j/123456789';
const startZoomUrl = 'https://zoom.us/s/123456789';
const personalZoomUrl = 'https://zoom.us/my/hunter-zoloman';
const randomUrl = 'https://zoom.us.com';
describe('PinnedLinks', () => {
let wrapper;
const link = {
get text() {
return wrapper.find(GlLink).text();
},
get href() {
return wrapper.find(GlLink).attributes('href');
},
};
const createComponent = props => {
wrapper = shallowMount(localVue.extend(PinnedLinks), {
localVue,
sync: false,
propsData: {
descriptionHtml: '',
...props,
},
});
};
it('displays Zoom link', () => {
createComponent({
descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`,
});
expect(link.text).toBe('Join Zoom meeting');
});
it('detects plain Zoom link', () => {
createComponent({
descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`,
});
expect(link.href).toBe(plainZoomUrl);
});
it('detects vanity Zoom link', () => {
createComponent({
descriptionHtml: `<a href="${vanityZoomUrl}">Zoom</a>`,
});
expect(link.href).toBe(vanityZoomUrl);
});
it('detects Zoom start meeting link', () => {
createComponent({
descriptionHtml: `<a href="${startZoomUrl}">Zoom</a>`,
});
expect(link.href).toBe(startZoomUrl);
});
it('detects personal Zoom room link', () => {
createComponent({
descriptionHtml: `<a href="${personalZoomUrl}">Zoom</a>`,
});
expect(link.href).toBe(personalZoomUrl);
});
it('only renders final Zoom link in description', () => {
createComponent({
descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a><a href="${vanityZoomUrl}">Zoom</a>`,
});
expect(link.href).toBe(vanityZoomUrl);
});
it('does not render for other links', () => {
createComponent({
descriptionHtml: `<a href="${randomUrl}">Some other link</a>`,
});
expect(wrapper.find(GlLink).exists()).toBe(false);
});
});
...@@ -700,10 +700,10 @@ ...@@ -700,10 +700,10 @@
dependencies: dependencies:
requireindex "~1.1.0" requireindex "~1.1.0"
"@gitlab/svgs@^1.63.0": "@gitlab/svgs@^1.64.0":
version "1.63.0" version "1.64.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.63.0.tgz#9dd544026d203e4ce6efed72b05db68f710c4d49" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.64.0.tgz#1370bcbe9ca0ecc9fb919956cd4241bea090ddd3"
integrity sha512-YztrReFTg31B7v5wtUC5j15KHNcMebtW+kACytEU42XomMaIwk4USIbygqWlq0VRHA2VHJrHApfJHIjxiCCQcA== integrity sha512-y9p73NGDnQJc18Dtk0oJfgxedancBT6UceATcnZMceLV6iWylzdMbQWxCl4O2aBXwsAoCrLUJQ9jhRkbNicYNA==
"@gitlab/ui@^3.11.0": "@gitlab/ui@^3.11.0":
version "3.11.0" version "3.11.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment