-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cloud): templates as replace menu entries
Closes #204
- Loading branch information
Showing
5 changed files
with
1,113 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { | ||
getBusinessObject, | ||
isAny | ||
} from 'bpmn-js/lib/util/ModelUtil'; | ||
|
||
/** | ||
* A replace menu provider that allows to replace elements with | ||
* element templates. | ||
*/ | ||
function ElementTemplatesReplaceProvider(popupMenu, translate, elementTemplates) { | ||
|
||
this._popupMenu = popupMenu; | ||
this._translate = translate; | ||
this._elementTemplates = elementTemplates; | ||
|
||
this.register(); | ||
} | ||
|
||
ElementTemplatesReplaceProvider.$inject = [ | ||
'popupMenu', | ||
'translate', | ||
'elementTemplates' | ||
]; | ||
|
||
|
||
/** | ||
* Register replace menu provider in the popup menu | ||
*/ | ||
ElementTemplatesReplaceProvider.prototype.register = function() { | ||
this._popupMenu.registerProvider('bpmn-replace', this); | ||
}; | ||
|
||
ElementTemplatesReplaceProvider.prototype._getMatchingTemplates = function(element) { | ||
return this._elementTemplates.getLatest().filter(template => { | ||
return isAny(element, template.appliesTo) && !isTemplateApplied(element, template); | ||
}); | ||
}; | ||
|
||
/** | ||
* Get all element templates that can be used to replace the given element. | ||
* | ||
* @param {djs.model.Base} element | ||
* | ||
* @return {Array<Object>} a list of element templates as menu entries | ||
*/ | ||
ElementTemplatesReplaceProvider.prototype.getEntries = function(element) { | ||
|
||
const templates = this._getMatchingTemplates(element); | ||
return templates.map(template => { | ||
|
||
const { | ||
id, | ||
icon = {}, | ||
} = template; | ||
|
||
const group = { | ||
id: 'templates', | ||
name: this._translate('Templates') | ||
}; | ||
|
||
return { | ||
...template, | ||
group, | ||
id: `replace-with-template-${id}`, | ||
imageUrl: sanitizeImageUrl(icon.contents), | ||
action: () => { | ||
this._elementTemplates.applyTemplate(element, template); | ||
} | ||
}; | ||
}); | ||
}; | ||
|
||
|
||
export default { | ||
__init__: [ 'elementTemplatesProvider' ], | ||
elementTemplatesProvider: [ 'type', ElementTemplatesReplaceProvider ] | ||
}; | ||
|
||
|
||
// helpers //////////// | ||
function sanitizeImageUrl(url) { | ||
if (url && /^(https?|data):/.test(url)) { | ||
return url; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
export function isTemplateApplied(element, template) { | ||
const businessObject = getBusinessObject(element); | ||
|
||
if (businessObject) { | ||
return businessObject.get('zeebe:modelerTemplate') === template.id; | ||
} | ||
|
||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
259 changes: 259 additions & 0 deletions
259
test/camunda-cloud/ElementTemplatesReplaceProviderSpec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,259 @@ | ||
import { | ||
inject, | ||
getBpmnJS, | ||
bootstrapCamundaCloudModeler | ||
} from 'test/TestHelper'; | ||
|
||
import { | ||
attr as domAttr, | ||
queryAll as domQueryAll, | ||
query as domQuery | ||
} from 'min-dom'; | ||
|
||
import { | ||
isTemplateApplied | ||
} from 'lib/camunda-cloud/ElementTemplatesReplaceProvider'; | ||
|
||
import simpleXml from 'test/fixtures/replaceElementTemplates.bpmn'; | ||
import templates from './replace-element-templates.json'; | ||
|
||
|
||
describe('<ElementTemplatesReplaceProvider>', function() { | ||
|
||
beforeEach(bootstrapCamundaCloudModeler( | ||
simpleXml | ||
)); | ||
|
||
beforeEach(inject(function(elementTemplates) { | ||
elementTemplates.set(templates); | ||
})); | ||
|
||
|
||
describe('display', function() { | ||
|
||
it('should display matching element templates', inject(function(elementRegistry) { | ||
|
||
// given | ||
const task = elementRegistry.get('ServiceTask_1'); | ||
|
||
// when | ||
openPopup(task); | ||
|
||
// then | ||
expect(queryTemplateEntries()).to.have.length(5); | ||
|
||
})); | ||
|
||
|
||
it('should not display element templates that do not apply', inject(function(elementRegistry) { | ||
|
||
// given | ||
const task = elementRegistry.get('Task_1'); | ||
|
||
// when | ||
openPopup(task); | ||
|
||
// then | ||
expect(queryTemplateEntries()).to.have.length(4); | ||
|
||
})); | ||
|
||
|
||
it('should not display applied element template', inject(function(elementRegistry, elementTemplates) { | ||
|
||
// given | ||
const task = elementRegistry.get('ServiceTask_1'); | ||
elementTemplates.applyTemplate(task, templates[0]); | ||
|
||
// when | ||
openPopup(task); | ||
|
||
// then | ||
expect(queryTemplateEntries()).to.have.length(4); | ||
|
||
})); | ||
|
||
}); | ||
|
||
|
||
describe('options', function() { | ||
|
||
beforeEach(inject(function(elementRegistry) { | ||
|
||
// given | ||
const task = elementRegistry.get('ServiceTask_1'); | ||
|
||
// when | ||
openPopup(task); | ||
|
||
})); | ||
|
||
it('should display title', function() { | ||
|
||
// given | ||
const template = templates[0]; | ||
const entry = queryEntry(`replace-with-template-${template.id}`); | ||
|
||
// then | ||
expect(domQuery('.djs-popup-entry-name', entry).textContent).to.eql(template.name); | ||
|
||
}); | ||
|
||
|
||
it('should display icon', function() { | ||
|
||
// given | ||
const template = templates[0]; | ||
const entry = queryEntry(`replace-with-template-${template.id}`); | ||
const icon = domQuery('.djs-popup-entry-icon', entry); | ||
|
||
// then | ||
expect(icon.getAttribute('src')).to.eql(template.icon.contents); | ||
|
||
}); | ||
|
||
|
||
it('should display description', function() { | ||
|
||
// given | ||
const template = templates[0]; | ||
const entry = queryEntry(`replace-with-template-${template.id}`); | ||
const description = domQuery('.djs-popup-entry-description', entry); | ||
|
||
// then | ||
expect(description.textContent).to.eql(template.description); | ||
|
||
}); | ||
|
||
|
||
it('should display documentation link', function() { | ||
|
||
// given | ||
const template = templates[0]; | ||
const entry = queryEntry(`replace-with-template-${template.id}`); | ||
const link = domQuery('.djs-popup-entry-docs a', entry); | ||
|
||
// then | ||
expect(link.getAttribute('href')).to.eql(template.documentationRef); | ||
|
||
}); | ||
|
||
|
||
it('should display group', function() { | ||
|
||
// given | ||
const group = domQuery('.djs-popup-group[data-group="templates"'); | ||
|
||
// then | ||
expect(group).to.exist; | ||
expect(group.children).to.have.lengthOf(5); | ||
}); | ||
|
||
}); | ||
|
||
|
||
describe('replace', function() { | ||
|
||
it('should apply template', inject(function(elementRegistry) { | ||
|
||
// given | ||
const task = elementRegistry.get('ServiceTask_1'); | ||
|
||
// when | ||
openPopup(task); | ||
|
||
triggerAction(`replace-with-template-${templates[0].id}`); | ||
|
||
// then | ||
expect(isTemplateApplied(task, templates[0])).to.be.true; | ||
})); | ||
|
||
|
||
it('should undo', inject(function(elementRegistry, commandStack) { | ||
|
||
// given | ||
const task = elementRegistry.get('ServiceTask_1'); | ||
openPopup(task); | ||
triggerAction(`replace-with-template-${templates[0].id}`); | ||
|
||
// when | ||
commandStack.undo(); | ||
|
||
// then | ||
expect(isTemplateApplied(task, templates[0])).to.be.false; | ||
})); | ||
|
||
|
||
it('should redo', inject(function(elementRegistry, commandStack) { | ||
|
||
// given | ||
const task = elementRegistry.get('ServiceTask_1'); | ||
openPopup(task); | ||
triggerAction(`replace-with-template-${templates[0].id}`); | ||
|
||
// when | ||
commandStack.undo(); | ||
commandStack.redo(); | ||
|
||
// then | ||
expect(isTemplateApplied(task, templates[0])).to.be.true; | ||
|
||
})); | ||
|
||
}); | ||
|
||
}); | ||
|
||
|
||
// helpers //////////// | ||
|
||
function openPopup(element, offset) { | ||
offset = offset || 100; | ||
|
||
getBpmnJS().invoke(function(popupMenu) { | ||
popupMenu.open(element, 'bpmn-replace', { | ||
x: element.x, y: element.y | ||
}); | ||
|
||
}); | ||
} | ||
|
||
function queryEntry(id) { | ||
var container = getMenuContainer(); | ||
|
||
return domQuery('.djs-popup [data-id="' + id + '"]', container); | ||
} | ||
|
||
function queryTemplateEntries() { | ||
var container = getMenuContainer(); | ||
|
||
const entries = Array.from(domQueryAll('.djs-popup-body .entry', container)); | ||
return entries.filter(function(entry) { | ||
return domAttr(entry, 'data-id').startsWith('replace-with-template'); | ||
}); | ||
} | ||
|
||
function getMenuContainer() { | ||
const popup = getBpmnJS().get('popupMenu'); | ||
return popup._current.container; | ||
} | ||
|
||
|
||
function triggerAction(id) { | ||
var entry = queryEntry(id); | ||
|
||
if (!entry) { | ||
throw new Error('entry "' + id + '" not found in replace menu'); | ||
} | ||
|
||
var popupMenu = getBpmnJS().get('popupMenu'); | ||
var eventBus = getBpmnJS().get('eventBus'); | ||
|
||
return popupMenu.trigger( | ||
eventBus.createEvent({ | ||
target: entry, | ||
x: 0, | ||
y: 0, | ||
}) | ||
); | ||
} |
Oops, something went wrong.