import 'clay-icon';
import Soy from 'metal-soy';
import PortletBase from 'frontend-js-web/liferay/PortletBase.es';
import {Config} from 'metal-state';
import {openToast} from 'frontend-js-web/liferay/toast/commands/OpenToast.es';
import {PublishChangeList} from './PublishChangeList.es';
import templates from './Overview.soy';
const SPLIT_REGEX = /({\d+})/g;
/**
* Provides the component for the Overview configuration screen.
*/
class Overview extends PortletBase {
created() {
this._render();
this._fetchProductionCollection();
}
_checkoutCollection(ctCollectionId, production) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-CSRF-Token', Liferay.authToken);
let body = {
credentials: 'include',
headers,
method: 'POST'
};
let url = this.urlCollectionsBase + '/' + ctCollectionId + '/checkout?companyId=' + Liferay.ThemeDisplay.getCompanyId() + '&userId=' + Liferay.ThemeDisplay.getUserId();
fetch(url, body)
.then(
response => {
if (response.status === 202) {
Liferay.fire('refreshChangeTrackingIndicator');
if (production) {
Liferay.Util.navigate(this.urlSelectProduction);
}
else {
this._render();
}
}
else if (response.status === 400) {
response.json()
.then(
data => {
openToast(
{
message: Liferay.Util.sub(Liferay.Language.get('an-error-occured-when-trying-to-check-x-out-x'), this.changeListName, data.message),
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
);
}
}
)
.catch(
error => {
const message = typeof error === 'string' ?
error :
Liferay.Util.sub(Liferay.Language.get('an-error-occured-when-trying-to-check-x-out'), this.changeListName);
openToast(
{
message,
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
);
}
_fetchProductionCollection() {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-CSRF-Token', Liferay.authToken);
let init = {
credentials: 'include',
headers,
method: 'GET'
};
let url = this.urlCollectionsBase + '?companyId=' + Liferay.ThemeDisplay.getCompanyId() + '&type=production';
fetch(url, init)
.then(r => r.json())
.then(
response => {
this.productionCTCollectionId = response[0].ctCollectionId;
}
);
}
_fetchAll(urls, init) {
return Promise.all(
urls.map(
url => fetch(url, init)
.then(r => r.json())
.then(data => data)
.catch(
error => {
const message = typeof error === 'string' ?
error :
Liferay.Util.sub(Liferay.Language.get('an-error-occured-while-getting-data-from-x'), url);
openToast(
{
message,
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
)
)
);
}
_fetchChangeEntries(url, type) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-CSRF-Token', Liferay.authToken);
let init = {
credentials: 'include',
headers,
method: type
};
fetch(url, init)
.then(r => r.json())
.then(response => this._populateChangeEntries(response))
.catch(
error => {
const message = typeof error === 'string' ?
error :
Liferay.Util.sub(Liferay.Language.get('an-error-occured-while-getting-data-from-x'), url);
openToast(
{
message,
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
);
}
_fetchCollisions(url, type) {
this.collisionsLoading = true;
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-CSRF-Token', Liferay.authToken);
let init = {
credentials: 'include',
headers,
method: type
};
fetch(url, init)
.then(r => r.json())
.then(response => this._populateCollidingChangeEntries(response))
.catch(
error => {
const message = typeof error === 'string' ?
error :
Liferay.Util.sub(Liferay.Language.get('an-error-occured-while-getting-data-from-x'), url);
openToast(
{
message,
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
);
}
_fetchRecentCollections(url, type) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-CSRF-Token', Liferay.authToken);
let init = {
credentials: 'include',
headers,
method: type
};
fetch(url, init)
.then(r => r.json())
.then(response => this._populateChangeListsDropdown(response))
.catch(
error => {
const message = typeof error === 'string' ?
error :
Liferay.Util.sub(Liferay.Language.get('an-error-occured-while-getting-data-from-x'), url);
openToast(
{
message,
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
);
}
_handleClickPublish(event) {
new PublishChangeList(
{
changeListDescription: this.descriptionActiveChangeList,
changeListHasCollision: this.hasCollision,
changeListName: this.headerTitleActiveChangeList,
spritemap: themeDisplay.getPathThemeImages() + '/lexicon/icons.svg',
urlChangeListsHistory: this.urlChangeListsHistory,
urlCheckoutProduction: this.urlCollectionsBase + '/' + this.productionCTCollectionId + '/checkout?companyId=' + Liferay.ThemeDisplay.getCompanyId() + '&userId=' + Liferay.ThemeDisplay.getUserId(),
urlPublishChangeList: this.urlActiveCollectionPublish
}
);
}
_handleClickRecentCollections(event) {
event.preventDefault();
let ok = true;
if (this.checkoutConfirmationEnabled) {
const label = this._sub(Liferay.Language.get('do-you-want-to-switch-to-x-change-list'), [event.target.text]) + '\n' + Liferay.Language.get('you-can-disable-this-message-from-the-change-list-user-settings-tab');
ok = confirm(label);
}
if (ok) {
let collectionId = event.target.getAttribute('data-collection-id');
let production = event.target.getAttribute('data-production');
this._checkoutCollection(collectionId, production);
}
}
_handleClickTrash() {
let ok = false;
const label = this._sub(Liferay.Language.get('are-you-sure-you-want-to-delete-x-change-list'), [this.headerTitleActiveChangeList]);
ok = confirm(label);
if (ok) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-CSRF-Token', Liferay.authToken);
let body = {
credentials: 'include',
headers,
method: 'DELETE'
};
let url = this.urlCollectionsBase + '/' + this.activeCTCollectionId;
fetch(url, body)
.then(
response => {
if (response.status === 204) {
Liferay.Util.navigate(this.urlSelectProduction);
}
else if (response.status === 404) {
openToast(
{
message: this._sub(Liferay.Language.get('unable-to-delete-change-list-x-because-it-could-not-be-found'), [this.headerTitleActiveChangeList]),
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
}
).catch(
error => {
const message = typeof error === 'string' ?
error :
this._sub(Liferay.Language.get('an-error-occured-when-trying-to-delete-x'), [this.headerTitleActiveChangeList]);
openToast(
{
message,
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
);
}
}
_populateChangeEntries(changeEntriesResult) {
this.changeEntries = [];
this.headerButtonDisabled = false;
if (!changeEntriesResult.items) {
this.headerButtonDisabled = true;
return;
}
changeEntriesResult.items.forEach(
changeEntry => {
let changeTypeStr = Liferay.Language.get('added');
if (changeEntry.changeType === 1) {
changeTypeStr = Liferay.Language.get('deleted');
}
else if (changeEntry.changeType === 2) {
changeTypeStr = Liferay.Language.get('modified');
}
let entityNameTranslation = this.entityNameTranslations.find(
entityNameTranslation =>
entityNameTranslation.key == changeEntry.contentType
);
this.changeEntries.push(
{
changeType: changeTypeStr,
conflict: changeEntry.collision,
contentType: entityNameTranslation.translation,
lastEdited: new Intl.DateTimeFormat(
Liferay.ThemeDisplay.getBCP47LanguageId(),
{
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
month: 'numeric',
year: 'numeric'
}).format(new Date(changeEntry.modifiedDate)),
site: changeEntry.siteName,
title: changeEntry.title,
userName: changeEntry.userName,
version: String(changeEntry.version)
}
);
}
);
if (this.changeEntries.length === 0) {
this.headerButtonDisabled = true;
}
}
_populateChangeListsDropdown(collectionResults) {
this.changeListsDropdownMenu = [];
collectionResults.forEach(
ctCollection => {
this.changeListsDropdownMenu.push(
{
ctCollectionId: ctCollection.ctCollectionId,
label: ctCollection.name
}
);
}
);
}
_populateCollidingChangeEntries(collisionsResult) {
if (collisionsResult.items) {
this.collisionsCount = collisionsResult.items.length;
if (this.collisionsCount > 0) {
this.hasCollision = true;
}
}
this.collisionsTooltip = Liferay.Util.sub(Liferay.Language.get('collision-detected-for-x-change-lists'), this.collisionsCount);
this.collisionsLoading = false;
}
_populateFields(requestResult) {
let activeCollection = requestResult[0];
let productionInformation = requestResult[1];
let userSettings = requestResult[2];
this.activeCTCollectionId = activeCollection[0].ctCollectionId;
if (activeCollection !== undefined && activeCollection.length == 1) {
activeCollection = activeCollection[0];
}
if (activeCollection !== undefined) {
let foundEntriesLink = activeCollection.links.find(
function(link) {
return link.rel === 'entries';
}
);
if (foundEntriesLink) {
this._fetchCollisions(foundEntriesLink.href + '?collision=true', foundEntriesLink.type);
this._fetchChangeEntries(foundEntriesLink.href, foundEntriesLink.type);
}
this.urlActiveCollectionPublish = activeCollection.links.find(
function(link) {
return link.rel === 'publish';
}
);
// Changes
this.changes = {
added: activeCollection.additionCount,
deleted: activeCollection.deletionCount,
modified: activeCollection.modificationCount
};
// Active Change List Description
this.descriptionActiveChangeList = activeCollection.description;
// Change Lists dropdown Menu
let urlRecentCollections = this.urlCollectionsBase + '?companyId=' + Liferay.ThemeDisplay.getCompanyId() + '&userId=' + Liferay.ThemeDisplay.getUserId() + '&type=recent&limit=5&sort=modifiedDate:desc';
this._fetchRecentCollections(urlRecentCollections, 'GET');
// Active Change List Header Title
this.headerTitleActiveChangeList = activeCollection.name;
// Initial Fetch
this.initialFetch = true;
}
if (productionInformation !== undefined && productionInformation.length == 1) {
productionInformation = productionInformation[0];
}
if ((productionInformation !== undefined) && (productionInformation.ctcollection !== undefined) && (productionInformation.ctcollection.name !== undefined)) {
this.descriptionProductionInformation = productionInformation.ctcollection.description;
this.headerTitleProductionInformation = productionInformation.ctcollection.name;
let publishDate = new Date(productionInformation.date);
this.publishedBy = {
dateTime: new Intl.DateTimeFormat(
Liferay.ThemeDisplay.getBCP47LanguageId(),
{
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
month: 'numeric',
year: 'numeric'
}).format(publishDate),
userInitials: productionInformation.userInitials,
userName: productionInformation.userName,
userPortraitURL: productionInformation.userPortraitURL
};
this.productionFound = true;
}
else {
this.productionFound = false;
}
if (userSettings) {
this.checkoutConfirmationEnabled = userSettings.checkoutCTCollectionConfirmationEnabled;
}
}
_render() {
let urlActiveCollection = this.urlCollectionsBase + '?companyId=' + Liferay.ThemeDisplay.getCompanyId() + '&userId=' + Liferay.ThemeDisplay.getUserId() + '&type=active';
let urls = [urlActiveCollection, this.urlProductionInformation, this.urlUserSettings];
this.initialFetch = false;
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-CSRF-Token', Liferay.authToken);
let init = {
credentials: 'include',
headers,
method: 'GET'
};
this._fetchAll(urls, init)
.then(result => this._populateFields(result))
.catch(
error => {
const message = typeof error === 'string' ?
error :
Liferay.Language.get('an-error-occured-while-parsing-data');
openToast(
{
message,
title: Liferay.Language.get('error'),
type: 'danger'
}
);
}
);
}
_sub(langKey, args) {
const keyArray = langKey.split(SPLIT_REGEX).filter(val => val.length !== 0);
for (let i = 0; i < args.length; i++) {
const arg = args[i];
const indexKey = `{${i}}`;
let argIndex = keyArray.indexOf(indexKey);
while (argIndex >= 0) {
keyArray.splice(argIndex, 1, arg);
argIndex = keyArray.indexOf(indexKey);
}
}
return keyArray.join('');
}
}
/**
* State definition.
*
* @static
* @type {!Object}
*/
Overview.STATE = {
/**
* Active change tracking collection ID retrieved from the REST service.
*
* @default 0
* @instance
* @memberOf Overview
* @type {number}
*/
activeCTCollectionId: Config.number().value(0),
/**
* Number of changes for the active change list.
*
* @default
* @instance
* @memberOf Overview
* @type {object}
*/
changes: Config.shapeOf(
{
added: Config.number().value(0),
deleted: Config.number().value(0),
modified: Config.number().value(0)
}
),
/**
* Active change list card description.
*
* @default undefined
* @instance
* @memberOf Overview
* @type {string}
*/
descriptionActiveChangeList: Config.string(),
/**
* Production card description.
*
* @default
* @instance
* @memberOf Overview
* @type {string}
*/
descriptionProductionInformation: Config.string(),
/**
* JSON array of translation properties.
*
* @default
* @instance
* @memberOf Overview
* @type {object}
*/
entityNameTranslations: Config.arrayOf(
Config.shapeOf(
{
key: Config.string(),
translation: Config.string()
}
)
),
/**
* Change entries for the currently selected change tracking collection.
*
* @default undefined
* @instance
* @memberOf Overview
* @type {object}
*/
changeEntries: Config.arrayOf(
Config.shapeOf(
{
changeType: Config.string(),
conflict: Config.bool(),
contentType: Config.string(),
lastEdited: Config.string(),
site: Config.string(),
title: Config.string(),
userName: Config.string(),
version: Config.string()
}
)
),
/**
* List of drop down menu items.
*
* @default []
* @instance
* @memberOf Overview
* @type {array}
*/
changeListsDropdownMenu: Config.arrayOf(
Config.shapeOf(
{
ctCollectionId: Config.string(),
label: Config.string()
}
)
),
/**
* Checkout confirmation is enabled.
*
* @default true
* @instance
* @memberOf Overview
* @type {boolean}
*/
checkoutConfirmationEnabled: Config.bool().value(true),
/**
* Number of collisions loaded (only stored if fetching is in progress).
*
* @default true
* @instance
* @memberOf Overview
* @type {boolean}
*/
collisionsLoading: Config.bool().value(true),
/**
* Number of collisions.
*
* @default true
* @instance
* @memberOf Overview
* @type {boolean}
*/
collisionsCount: Config.number().value(0),
collisionsTooltip: Config.string(),
hasCollision: Config.bool().value(false),
/**
* If <code>true</code>, head button is disabled.
*
* @default false
* @instance
* @memberOf Overview
* @type {boolean}
*/
headerButtonDisabled: Config.bool().value(false),
/**
* Active change list's card header title.
*
* @default undefined
* @instance
* @memberOf Overview
* @type {string}
*/
headerTitleActiveChangeList: Config.string(),
/**
* Production's card header title.
*
* @default
* @instance
* @memberOf Overview
* @type {string}
*/
headerTitleProductionInformation: Config.string(),
/**
* If <code>true</code>, an initial fetch has already occurred.
*
* @default false
* @instance
* @memberOf Overview
* @type {boolean}
*/
initialFetch: Config.bool().value(false),
productionCTCollectionId: Config.number(),
productionFound: Config.bool().value(false),
/**
* Information of who published to production.
*
* @instance
* @memberOf Overview
* @type {object}
*/
publisedBy: Config.shapeOf(
{
dateTime: Config.string(),
userInitials: Config.string(),
userName: Config.string(),
userPortraitURL: Config.string()
}
),
/**
* BBase REST API URL to the collection resource.
*
* @default
* @instance
* @memberOf Overview
* @type {string}
*/
urlCollectionsBase: Config.string(),
urlActiveCollectionPublish: Config.object(),
urlChangeListsHistory: Config.string().required(),
/**
* The URL for the REST service to the change entries.
* @default
* @instance
* @memberOf Overview
* @type {string}
*/
urlChangeEntries: Config.string(),
/**
* URL for the REST service to the change tracking production information.
*
* @default undefined
* @instance
* @memberOf Overview
* @type {!string}
*/
urlProductionInformation: Config.string().required(),
/**
* URL for the production view.
*
* @default undefined
* @instance
* @memberOf Overview
* @type {!string}
*/
urlProductionView: Config.string().required(),
/**
* URL for the list view with production checked out.
*
* @default undefined
* @instance
* @memberOf Overview
* @type {string}
*/
urlSelectProduction: Config.string(),
/**
* URL for the REST service to the user settings.
*
* @default undefined
* @instance
* @memberOf Overview
* @type {string}
*/
urlUserSettings: Config.string(),
/**
* Path of the available icons.
*
* @default undefined
* @instance
* @memberOf Overview
* @type {!string}
*/
spritemap: Config.string().required()
};
Soy.register(Overview, templates);
export {Overview};
export default Overview;