Skip to content

FOUR-14137 - Enable External Integration with BambooHR (develop) #6348

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

Merged
merged 9 commits into from
Mar 9, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All @@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All @@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All @@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All @@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All @@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All @@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All @@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All @@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading