diff --git a/app/assets/javascripts/app/controllers/_application_controller.js.coffee b/app/assets/javascripts/app/controllers/_application_controller.js.coffee index 078e3dd82..1820ef8a7 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.js.coffee @@ -467,6 +467,31 @@ class App.Controller extends Spine.Controller ws_send: (data) -> App.Event.trigger( 'ws:send', JSON.stringify(data) ) + # central method, is getting called on every ticket form change + ticketFormChanges: (params, attribute, attributes, classname, form, ui) => + if @form_meta.dependencies && @form_meta.dependencies[attribute.name] + dependency = @form_meta.dependencies[attribute.name][ parseInt(params[attribute.name]) ] + if !dependency + dependency = @form_meta.dependencies[attribute.name][ params[attribute.name] ] + if dependency + for fieldNameToChange of dependency + filter = [] + if dependency[fieldNameToChange] + filter = dependency[fieldNameToChange] + + # find element to replace + for item in attributes + if item.name is fieldNameToChange + item['filter'] = {} + item['filter'][ fieldNameToChange ] = filter + item.default = params[item.name] + #if !item.default + # delete item['default'] + newElement = ui.formGenItem( item, classname, form ) + + # replace new option list + form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith( newElement ) + class App.ControllerPermanent extends App.Controller constructor: -> super @@ -485,10 +510,10 @@ class App.ControllerModal extends App.Controller '.modal-body': 'body' events: - 'submit form': 'onSubmit' + 'submit form': 'onSubmit' 'click .js-submit:not(.is-disabled)': 'onSubmit' - 'click .js-cancel': 'hide' - 'click .js-close': 'hide' + 'click .js-cancel': 'hide' + 'click .js-close': 'hide' className: 'modal fade' diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee index 15d66648e..35397afa9 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee @@ -35,7 +35,11 @@ class App.ControllerForm extends App.Controller formGen: -> App.Log.debug 'ControllerForm', 'formGen', @model.configure_attributes - fieldset = $('
') + # check if own fieldset should be generated + if @noFieldset + fieldset = @el + else + fieldset = $('
') # collect form attributes @attributes = [] @@ -81,9 +85,6 @@ class App.ControllerForm extends App.Controller item = @formGenItem( attribute, className, fieldset, attribute_count ) item.appendTo(fieldset) - if @noFieldset - fieldset = fieldset.children() - if @fullForm if !@formClass @formClass = '' diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee index aa7632475..387657240 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee @@ -205,29 +205,6 @@ class App.TicketCreate extends App.Controller form_id: @form_id ) - formChanges = (params, attribute, attributes, classname, form, ui) => - if @form_meta.dependencies && @form_meta.dependencies[attribute.name] - dependency = @form_meta.dependencies[attribute.name][ parseInt(params[attribute.name]) ] - if dependency - - for fieldNameToChange of dependency - filter = [] - if dependency[fieldNameToChange] - filter = dependency[fieldNameToChange] - - # find element to replace - for item in attributes - if item.name is fieldNameToChange - item['filter'] = {} - item['filter'][ fieldNameToChange ] = filter - item.default = params[item.name] - #if !item.default - # delete item['default'] - newElement = ui.formGenItem( item, classname, form ) - - # replace new option list - form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith( newElement ) - signatureChanges = (params, attribute, attributes, classname, form, ui) => if attribute && attribute.name is 'group_id' signature = undefined @@ -274,7 +251,7 @@ class App.TicketCreate extends App.Controller events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges, + @ticketFormChanges, signatureChanges, ] filter: @form_meta.filter @@ -297,7 +274,7 @@ class App.TicketCreate extends App.Controller events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges, + @ticketFormChanges, signatureChanges, ] filter: @form_meta.filter @@ -312,7 +289,7 @@ class App.TicketCreate extends App.Controller events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges, + @ticketFormChanges, signatureChanges, ] filter: @form_meta.filter diff --git a/app/assets/javascripts/app/controllers/customer_ticket_create.js.coffee b/app/assets/javascripts/app/controllers/customer_ticket_create.js.coffee index 62b295826..fbcf2bd60 100644 --- a/app/assets/javascripts/app/controllers/customer_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/customer_ticket_create.js.coffee @@ -64,29 +64,6 @@ class Index extends App.ControllerContent groupFilter = [groupFilter] @form_meta.filter.group_id = groupFilter - formChanges = (params, attribute, attributes, classname, form, ui) => - if @form_meta.dependencies && @form_meta.dependencies[attribute.name] - dependency = @form_meta.dependencies[attribute.name][ parseInt(params[attribute.name]) ] - if dependency - - for fieldNameToChange of dependency - filter = [] - if dependency[fieldNameToChange] - filter = dependency[fieldNameToChange] - - # find element to replace - for item in attributes - if item.name is fieldNameToChange - item['filter'] = {} - item['filter'][ fieldNameToChange ] = filter - item.default = params[item.name] - #if !item.default - # delete item['default'] - newElement = ui.formGenItem( item, classname, form ) - - # replace new option list - form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith( newElement ) - @html App.view('customer_ticket_create')( head: 'New Ticket' ) new App.ControllerForm( @@ -95,7 +72,7 @@ class Index extends App.ControllerContent model: App.Ticket screen: 'create_top' handlers: [ - formChanges + @ticketFormChanges ] filter: @form_meta.filter autofocus: true @@ -115,7 +92,7 @@ class Index extends App.ControllerContent model: App.Ticket screen: 'create_middle' handlers: [ - formChanges + @ticketFormChanges ] filter: @form_meta.filter params: defaults diff --git a/app/assets/javascripts/app/controllers/layout_ref.js.coffee b/app/assets/javascripts/app/controllers/layout_ref.js.coffee index 9ff018978..1bc2bac8e 100644 --- a/app/assets/javascripts/app/controllers/layout_ref.js.coffee +++ b/app/assets/javascripts/app/controllers/layout_ref.js.coffee @@ -777,4 +777,16 @@ class insufficientRightsRef extends App.ControllerContent App.Config.set( 'layout_ref/insufficient_rights', insufficientRightsRef, 'Routes' ) + +class errorRef extends App.ControllerContent + + constructor: -> + super + @render() + + render: -> + @html App.view('layout_ref/error')() + +App.Config.set( 'layout_ref/error', errorRef, 'Routes' ) + App.Config.set( 'LayoutRef', { prio: 1700, parent: '#current_user', name: 'Layout Reference', target: '#layout_ref', role: [ 'Admin' ] }, 'NavBarRight' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/navigation.js.coffee b/app/assets/javascripts/app/controllers/navigation.js.coffee index 0d888dce8..53ede62f5 100644 --- a/app/assets/javascripts/app/controllers/navigation.js.coffee +++ b/app/assets/javascripts/app/controllers/navigation.js.coffee @@ -115,6 +115,9 @@ class App.Navigation extends App.Controller render: () -> + # reset result cache + @searchResultCache = {} + user = App.Session.get() @html App.view('navigation')( user: user @@ -127,6 +130,11 @@ class App.Navigation extends App.Controller @renderPersonal() searchFunction = => + + # use cache for search result + if @searchResultCache[@term] + @renderResult( @searchResultCache[@term] ) + App.Ajax.request( id: 'search' type: 'GET' @@ -139,6 +147,9 @@ class App.Navigation extends App.Controller # load assets App.Collection.loadAssets( data.assets ) + # cache search result + @searchResultCache[@term] = data.result + result = data.result for area in result if area.name is 'Ticket' @@ -222,7 +233,7 @@ class App.Navigation extends App.Controller return if term is @term @term = term @$('.search').toggleClass('filled', !!@term) - @delay( searchFunction, 220, 'search' ) + @delay( searchFunction, 200, 'search' ) ) # bind to empty search diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index 501225356..2a6f337b6 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -267,29 +267,6 @@ class App.TicketZoom extends App.Controller console.log('SHOW', ticket.id) el.find('.edit').html('') - formChanges = (params, attribute, attributes, classname, form, ui) => - if @form_meta.dependencies && @form_meta.dependencies[attribute.name] - dependency = @form_meta.dependencies[attribute.name][ parseInt(params[attribute.name]) ] - if dependency - - for fieldNameToChange of dependency - filter = [] - if dependency[fieldNameToChange] - filter = dependency[fieldNameToChange] - - # find element to replace - for item in attributes - if item.name is fieldNameToChange - item['filter'] = {} - item['filter'][ fieldNameToChange ] = filter - item.default = params[item.name] - #if !item.default - # delete item['default'] - newElement = ui.formGenItem( item, classname, form ) - - # replace new option list - form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith( newElement ) - defaults = ticket.attributes() task_state = @taskGet('ticket') modelDiff = @getDiff( defaults, task_state ) @@ -305,7 +282,7 @@ class App.TicketZoom extends App.Controller screen: 'edit' params: App.Ticket.find(ticket.id) handlers: [ - formChanges + @ticketFormChanges ] filter: @form_meta.filter params: defaults @@ -1502,10 +1479,10 @@ class ArticleView extends App.Controller e.preventDefault() # get reference article - article_id = $(e.target).parents('[data-id]').data('id') - article = App.TicketArticle.fullLocal( article_id ) - type = App.TicketArticleType.find( article.type_id ) - customer = App.User.find( article.created_by_id ) + article_id = $(e.target).parents('[data-id]').data('id') + article = App.TicketArticle.fullLocal( article_id ) + type = App.TicketArticleType.find( article.type_id ) + customer = App.User.find( article.created_by_id ) @ui.el.find('.article-add').ScrollTo() @@ -1534,12 +1511,18 @@ class ArticleView extends App.Controller to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid articleNew.to = to - else if type.name is 'email' + else if type.name is 'email' || type.name is 'phone' || type.name is 'web' + if article.sender.name is 'Agent' articleNew.to = article.to else articleNew.to = article.from + # if sender is customer but in article.from is no email, try to get + # customers email via customer user + if articleNew.to && !articleNew.to.match(/@/) + articleNew.to = article.created_by.email + # filter for uniq recipients recipientAddresses = {} recipient = emailAddresses.parseAddressList(articleNew.to) @@ -1678,22 +1661,28 @@ class Article extends App.Controller ] #if @article.type.name is 'note' # actions.push [] - if @article.type.name is 'email' + if @article.type.name is 'email' || @article.type.name is 'phone' || @article.type.name is 'web' actions.push { name: 'reply' type: 'reply' href: '#' } recipients = [] - if @article.to - localRecipients = emailAddresses.parseAddressList(@article.to) - if localRecipients - recipients = recipients.concat localRecipients + if @article.sender.name is 'Agent' + if @article.to + localRecipients = emailAddresses.parseAddressList(@article.to) + if localRecipients + recipients = recipients.concat localRecipients + else + if @article.from + localRecipients = emailAddresses.parseAddressList(@article.from) + if localRecipients + recipients = recipients.concat localRecipients if @article.cc localRecipients = emailAddresses.parseAddressList(@article.cc) if localRecipients recipients = recipients.concat localRecipients - if recipients.length > 0 + if recipients.length > 1 actions.push { name: 'reply all' type: 'replyAll' diff --git a/app/assets/javascripts/app/controllers/widget/ff_lt_35.js.coffee b/app/assets/javascripts/app/controllers/widget/ff_lt_35.js.coffee new file mode 100644 index 000000000..31260a917 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/ff_lt_35.js.coffee @@ -0,0 +1,10 @@ +class FFlt35 + constructor: -> + data = App.Browser.detection() + if data.browser is 'Firefox' && data.version && data.version < 35 + + # for firefox lower 35 we need to set a class to hide own dropdown images + # whole file can be removed after dropping firefox 34 and lower support + $('html').addClass('ff-lt-35') + +App.Config.set( 'aaa_ff-lt-35', FFlt35, 'Widgets' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee index ac7c0348e..2dd7e937e 100644 --- a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee +++ b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee @@ -47,6 +47,7 @@ class App.OnlineNotificationWidget extends App.Controller @toggle.append('
' + count.toString() + '
') markAllAsRead: => + @counterUpdate(0) @ajax( id: 'markAllAsRead' type: 'POST' @@ -63,14 +64,22 @@ class App.OnlineNotificationWidget extends App.Controller onShow: => @updateContent() - # set heigth of notification popover - height = $('#app').height() - $('.js-notificationsContainer').css('height', "#{height-12}px") - $('.js-notificationsContainer .arrow').css('top', '20px') + # set height of notification popover + notificationsContainer = $('.js-notificationsContainer') + heightApp = $('#app').height() + heightPopoverSpacer = 36 + heightPopoverHeader = notificationsContainer.find('.popover-notificationsHeader').outerHeight() + heightPopoverContent = notificationsContainer.find('.popover-content').get(0).scrollHeight + heightPopoverContentNew = heightPopoverContent + if (heightPopoverHeader + heightPopoverContent + heightPopoverSpacer) > heightApp + heightPopoverContentNew = heightApp - heightPopoverHeader - heightPopoverSpacer + notificationsContainer.addClass('is-overflowing') + else + notificationsContainer.removeClass('is-overflowing') + notificationsContainer.find('.popover-content').css('height', "#{heightPopoverContentNew}px") # close notification list on click $('.js-notificationsContainer').on('click', (e) => - #console.log('CL') @hidePopover() ) diff --git a/app/assets/javascripts/app/lib/base/modernizr.custom.63196.js b/app/assets/javascripts/app/lib/base/modernizr.custom.63196.js deleted file mode 100644 index 1d3d5aed3..000000000 --- a/app/assets/javascripts/app/lib/base/modernizr.custom.63196.js +++ /dev/null @@ -1,828 +0,0 @@ -/* Modernizr 2.8.3 (Custom Build) | MIT & BSD - * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load - */ -; - - - -window.Modernizr = (function( window, document, undefined ) { - - var version = '2.8.3', - - Modernizr = {}, - - enableClasses = true, - - docElement = document.documentElement, - - mod = 'modernizr', - modElem = document.createElement(mod), - mStyle = modElem.style, - - inputElem = document.createElement('input') , - - smile = ':)', - - toString = {}.toString, - - prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), - - - - omPrefixes = 'Webkit Moz O ms', - - cssomPrefixes = omPrefixes.split(' '), - - domPrefixes = omPrefixes.toLowerCase().split(' '), - - ns = {'svg': 'http://www.w3.org/2000/svg'}, - - tests = {}, - inputs = {}, - attrs = {}, - - classes = [], - - slice = classes.slice, - - featureName, - - - injectElementWithStyles = function( rule, callback, nodes, testnames ) { - - var style, ret, node, docOverflow, - div = document.createElement('div'), - body = document.body, - fakeBody = body || document.createElement('body'); - - if ( parseInt(nodes, 10) ) { - while ( nodes-- ) { - node = document.createElement('div'); - node.id = testnames ? testnames[nodes] : mod + (nodes + 1); - div.appendChild(node); - } - } - - style = ['­',''].join(''); - div.id = mod; - (body ? div : fakeBody).innerHTML += style; - fakeBody.appendChild(div); - if ( !body ) { - fakeBody.style.background = ''; - fakeBody.style.overflow = 'hidden'; - docOverflow = docElement.style.overflow; - docElement.style.overflow = 'hidden'; - docElement.appendChild(fakeBody); - } - - ret = callback(div, rule); - if ( !body ) { - fakeBody.parentNode.removeChild(fakeBody); - docElement.style.overflow = docOverflow; - } else { - div.parentNode.removeChild(div); - } - - return !!ret; - - }, - - - - isEventSupported = (function() { - - var TAGNAMES = { - 'select': 'input', 'change': 'input', - 'submit': 'form', 'reset': 'form', - 'error': 'img', 'load': 'img', 'abort': 'img' - }; - - function isEventSupported( eventName, element ) { - - element = element || document.createElement(TAGNAMES[eventName] || 'div'); - eventName = 'on' + eventName; - - var isSupported = eventName in element; - - if ( !isSupported ) { - if ( !element.setAttribute ) { - element = document.createElement('div'); - } - if ( element.setAttribute && element.removeAttribute ) { - element.setAttribute(eventName, ''); - isSupported = is(element[eventName], 'function'); - - if ( !is(element[eventName], 'undefined') ) { - element[eventName] = undefined; - } - element.removeAttribute(eventName); - } - } - - element = null; - return isSupported; - } - return isEventSupported; - })(), - - - _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; - - if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { - hasOwnProp = function (object, property) { - return _hasOwnProperty.call(object, property); - }; - } - else { - hasOwnProp = function (object, property) { - return ((property in object) && is(object.constructor.prototype[property], 'undefined')); - }; - } - - - if (!Function.prototype.bind) { - Function.prototype.bind = function bind(that) { - - var target = this; - - if (typeof target != "function") { - throw new TypeError(); - } - - var args = slice.call(arguments, 1), - bound = function () { - - if (this instanceof bound) { - - var F = function(){}; - F.prototype = target.prototype; - var self = new F(); - - var result = target.apply( - self, - args.concat(slice.call(arguments)) - ); - if (Object(result) === result) { - return result; - } - return self; - - } else { - - return target.apply( - that, - args.concat(slice.call(arguments)) - ); - - } - - }; - - return bound; - }; - } - - function setCss( str ) { - mStyle.cssText = str; - } - - function setCssAll( str1, str2 ) { - return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); - } - - function is( obj, type ) { - return typeof obj === type; - } - - function contains( str, substr ) { - return !!~('' + str).indexOf(substr); - } - - function testProps( props, prefixed ) { - for ( var i in props ) { - var prop = props[i]; - if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { - return prefixed == 'pfx' ? prop : true; - } - } - return false; - } - - function testDOMProps( props, obj, elem ) { - for ( var i in props ) { - var item = obj[props[i]]; - if ( item !== undefined) { - - if (elem === false) return props[i]; - - if (is(item, 'function')){ - return item.bind(elem || obj); - } - - return item; - } - } - return false; - } - - function testPropsAll( prop, prefixed, elem ) { - - var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), - props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); - - if(is(prefixed, "string") || is(prefixed, "undefined")) { - return testProps(props, prefixed); - - } else { - props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); - return testDOMProps(props, prefixed, elem); - } - } tests['flexbox'] = function() { - return testPropsAll('flexWrap'); - }; tests['canvas'] = function() { - var elem = document.createElement('canvas'); - return !!(elem.getContext && elem.getContext('2d')); - }; - - tests['canvastext'] = function() { - return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); - }; - - - - tests['webgl'] = function() { - return !!window.WebGLRenderingContext; - }; - - - tests['touch'] = function() { - var bool; - - if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { - bool = true; - } else { - injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { - bool = node.offsetTop === 9; - }); - } - - return bool; - }; - - - - tests['geolocation'] = function() { - return 'geolocation' in navigator; - }; - - - tests['postmessage'] = function() { - return !!window.postMessage; - }; - - - tests['websqldatabase'] = function() { - return !!window.openDatabase; - }; - - tests['indexedDB'] = function() { - return !!testPropsAll("indexedDB", window); - }; - - tests['hashchange'] = function() { - return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); - }; - - tests['history'] = function() { - return !!(window.history && history.pushState); - }; - - tests['draganddrop'] = function() { - var div = document.createElement('div'); - return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); - }; - - tests['websockets'] = function() { - return 'WebSocket' in window || 'MozWebSocket' in window; - }; - - - tests['rgba'] = function() { - setCss('background-color:rgba(150,255,150,.5)'); - - return contains(mStyle.backgroundColor, 'rgba'); - }; - - tests['hsla'] = function() { - setCss('background-color:hsla(120,40%,100%,.5)'); - - return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); - }; - - tests['multiplebgs'] = function() { - setCss('background:url(https://),url(https://),red url(https://)'); - - return (/(url\s*\(.*?){3}/).test(mStyle.background); - }; tests['backgroundsize'] = function() { - return testPropsAll('backgroundSize'); - }; - - tests['borderimage'] = function() { - return testPropsAll('borderImage'); - }; - - - - tests['borderradius'] = function() { - return testPropsAll('borderRadius'); - }; - - tests['boxshadow'] = function() { - return testPropsAll('boxShadow'); - }; - - tests['textshadow'] = function() { - return document.createElement('div').style.textShadow === ''; - }; - - - tests['opacity'] = function() { - setCssAll('opacity:.55'); - - return (/^0.55$/).test(mStyle.opacity); - }; - - - tests['cssanimations'] = function() { - return testPropsAll('animationName'); - }; - - - tests['csscolumns'] = function() { - return testPropsAll('columnCount'); - }; - - - tests['cssgradients'] = function() { - var str1 = 'background-image:', - str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', - str3 = 'linear-gradient(left top,#9f9, white);'; - - setCss( - (str1 + '-webkit- '.split(' ').join(str2 + str1) + - prefixes.join(str3 + str1)).slice(0, -str1.length) - ); - - return contains(mStyle.backgroundImage, 'gradient'); - }; - - - tests['cssreflections'] = function() { - return testPropsAll('boxReflect'); - }; - - - tests['csstransforms'] = function() { - return !!testPropsAll('transform'); - }; - - - tests['csstransforms3d'] = function() { - - var ret = !!testPropsAll('perspective'); - - if ( ret && 'webkitPerspective' in docElement.style ) { - - injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { - ret = node.offsetLeft === 9 && node.offsetHeight === 3; - }); - } - return ret; - }; - - - tests['csstransitions'] = function() { - return testPropsAll('transition'); - }; - - - - tests['fontface'] = function() { - var bool; - - injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { - var style = document.getElementById('smodernizr'), - sheet = style.sheet || style.styleSheet, - cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; - - bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; - }); - - return bool; - }; - - tests['generatedcontent'] = function() { - var bool; - - injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) { - bool = node.offsetHeight >= 3; - }); - - return bool; - }; - tests['video'] = function() { - var elem = document.createElement('video'), - bool = false; - - try { - if ( bool = !!elem.canPlayType ) { - bool = new Boolean(bool); - bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); - - bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); - - bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); - } - - } catch(e) { } - - return bool; - }; - - tests['audio'] = function() { - var elem = document.createElement('audio'), - bool = false; - - try { - if ( bool = !!elem.canPlayType ) { - bool = new Boolean(bool); - bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); - bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,''); - - bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); - bool.m4a = ( elem.canPlayType('audio/x-m4a;') || - elem.canPlayType('audio/aac;')) .replace(/^no$/,''); - } - } catch(e) { } - - return bool; - }; - - - tests['localstorage'] = function() { - try { - localStorage.setItem(mod, mod); - localStorage.removeItem(mod); - return true; - } catch(e) { - return false; - } - }; - - tests['sessionstorage'] = function() { - try { - sessionStorage.setItem(mod, mod); - sessionStorage.removeItem(mod); - return true; - } catch(e) { - return false; - } - }; - - - tests['webworkers'] = function() { - return !!window.Worker; - }; - - - tests['applicationcache'] = function() { - return !!window.applicationCache; - }; - - - tests['svg'] = function() { - return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; - }; - - tests['inlinesvg'] = function() { - var div = document.createElement('div'); - div.innerHTML = ''; - return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; - }; - - tests['smil'] = function() { - return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); - }; - - - tests['svgclippaths'] = function() { - return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); - }; - - function webforms() { - Modernizr['input'] = (function( props ) { - for ( var i = 0, len = props.length; i < len; i++ ) { - attrs[ props[i] ] = !!(props[i] in inputElem); - } - if (attrs.list){ - attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement); - } - return attrs; - })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); - Modernizr['inputtypes'] = (function(props) { - - for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { - - inputElem.setAttribute('type', inputElemType = props[i]); - bool = inputElem.type !== 'text'; - - if ( bool ) { - - inputElem.value = smile; - inputElem.style.cssText = 'position:absolute;visibility:hidden;'; - - if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { - - docElement.appendChild(inputElem); - defaultView = document.defaultView; - - bool = defaultView.getComputedStyle && - defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && - (inputElem.offsetHeight !== 0); - - docElement.removeChild(inputElem); - - } else if ( /^(search|tel)$/.test(inputElemType) ){ - } else if ( /^(url|email)$/.test(inputElemType) ) { - bool = inputElem.checkValidity && inputElem.checkValidity() === false; - - } else { - bool = inputElem.value != smile; - } - } - - inputs[ props[i] ] = !!bool; - } - return inputs; - })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); - } - for ( var feature in tests ) { - if ( hasOwnProp(tests, feature) ) { - featureName = feature.toLowerCase(); - Modernizr[featureName] = tests[feature](); - - classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); - } - } - - Modernizr.input || webforms(); - - - Modernizr.addTest = function ( feature, test ) { - if ( typeof feature == 'object' ) { - for ( var key in feature ) { - if ( hasOwnProp( feature, key ) ) { - Modernizr.addTest( key, feature[ key ] ); - } - } - } else { - - feature = feature.toLowerCase(); - - if ( Modernizr[feature] !== undefined ) { - return Modernizr; - } - - test = typeof test == 'function' ? test() : test; - - if (typeof enableClasses !== "undefined" && enableClasses) { - docElement.className += ' ' + (test ? '' : 'no-') + feature; - } - Modernizr[feature] = test; - - } - - return Modernizr; - }; - - - setCss(''); - modElem = inputElem = null; - - ;(function(window, document) { - var version = '3.7.0'; - - var options = window.html5 || {}; - - var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; - - var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; - - var supportsHtml5Styles; - - var expando = '_html5shiv'; - - var expanID = 0; - - var expandoData = {}; - - var supportsUnknownElements; - - (function() { - try { - var a = document.createElement('a'); - a.innerHTML = ''; - supportsHtml5Styles = ('hidden' in a); - - supportsUnknownElements = a.childNodes.length == 1 || (function() { - (document.createElement)('a'); - var frag = document.createDocumentFragment(); - return ( - typeof frag.cloneNode == 'undefined' || - typeof frag.createDocumentFragment == 'undefined' || - typeof frag.createElement == 'undefined' - ); - }()); - } catch(e) { - supportsHtml5Styles = true; - supportsUnknownElements = true; - } - - }()); - - function addStyleSheet(ownerDocument, cssText) { - var p = ownerDocument.createElement('p'), - parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; - - p.innerHTML = 'x'; - return parent.insertBefore(p.lastChild, parent.firstChild); - } - - function getElements() { - var elements = html5.elements; - return typeof elements == 'string' ? elements.split(' ') : elements; - } - - function getExpandoData(ownerDocument) { - var data = expandoData[ownerDocument[expando]]; - if (!data) { - data = {}; - expanID++; - ownerDocument[expando] = expanID; - expandoData[expanID] = data; - } - return data; - } - - function createElement(nodeName, ownerDocument, data){ - if (!ownerDocument) { - ownerDocument = document; - } - if(supportsUnknownElements){ - return ownerDocument.createElement(nodeName); - } - if (!data) { - data = getExpandoData(ownerDocument); - } - var node; - - if (data.cache[nodeName]) { - node = data.cache[nodeName].cloneNode(); - } else if (saveClones.test(nodeName)) { - node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); - } else { - node = data.createElem(nodeName); - } - - return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; - } - - function createDocumentFragment(ownerDocument, data){ - if (!ownerDocument) { - ownerDocument = document; - } - if(supportsUnknownElements){ - return ownerDocument.createDocumentFragment(); - } - data = data || getExpandoData(ownerDocument); - var clone = data.frag.cloneNode(), - i = 0, - elems = getElements(), - l = elems.length; - for(;i -

<%- @T('Opps.. I\'m sorry, something went wrong!' ) %>

-

<%- @T('Status Code') %>: <%= @status %>

-

<%= @detail %>

+

<%- @T('Status Code') %>: <%= @status %>. <%= @detail %>

\ No newline at end of file diff --git a/app/assets/javascripts/app/views/layout_ref/error.jst.eco b/app/assets/javascripts/app/views/layout_ref/error.jst.eco new file mode 100644 index 000000000..6db816262 --- /dev/null +++ b/app/assets/javascripts/app/views/layout_ref/error.jst.eco @@ -0,0 +1,3 @@ +
+

<%- @T('Status Code') %>: 1234. may be not internet connection...

+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/layout_ref/index.jst.eco b/app/assets/javascripts/app/views/layout_ref/index.jst.eco index 6305abbf3..921d98bb4 100644 --- a/app/assets/javascripts/app/views/layout_ref/index.jst.eco +++ b/app/assets/javascripts/app/views/layout_ref/index.jst.eco @@ -22,6 +22,7 @@
  • Local Modal
  • Loading Placeholder
  • Insufficient Rights Warning
  • +
  • Error
  • \ No newline at end of file diff --git a/app/assets/javascripts/app/views/task_widget_tasks.jst.eco b/app/assets/javascripts/app/views/task_widget_tasks.jst.eco index 4eb28dadf..127d3cfe3 100644 --- a/app/assets/javascripts/app/views/task_widget_tasks.jst.eco +++ b/app/assets/javascripts/app/views/task_widget_tasks.jst.eco @@ -1,7 +1,7 @@ <% for item in @item_list: %> - +
    -
    +
    <%= item.data.head %>
    diff --git a/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco index bbd619500..dd0660010 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco @@ -86,7 +86,7 @@ <% if article.actions: %>
    -
    +
    <% for action in article.actions: %> <%- @T( action.name ) %> diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index ff187342e..9d18ab2a7 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -284,6 +284,7 @@ span[data-tooltip]:hover:before { &.btn--action { @extend label; + height: 31px; padding: 7px 11px 5px !important; } @@ -732,16 +733,17 @@ textarea, Firefox only hack ----------------- - Firefox doesn't allow us to hide the dropdown arrow - but we want to replace it with our own icon. - So we have to hide our own icon in Firefox. + Firefox below version 35 doesn't allow us to + hide the dropdown arrow but we want to replace + it with our own icon. So we have to hide our own + icon in Firefox versions under 35. + + The class is set via Javascript */ - @-moz-document url-prefix() { - .form-control + .select-arrow { - display: none; - } + html.ff-lt-35 .form-control + .select-arrow { + display: none; } select::-ms-expand { @@ -843,7 +845,6 @@ textarea, margin-top: 6px; margin-left: auto; @extend .horizontal; - @extend .justify; @extend .self-start; .btn { @@ -1336,17 +1337,19 @@ ol.tabs li { height: 6px; border-radius: 100%; background: #2c2d36; + will-change: opacity; + transform: translateZ(0); } .modified.priority.icon:after { - -webkit-animation: fade 1s ease 2s infinite alternate; - -moz-animation: fade 1s ease 2s infinite alternate; - animation: fade 1s ease 2s infinite alternate; + -webkit-animation: fade 2s ease-in-out infinite; + -moz-animation: fade 2s ease-in-out infinite; + animation: fade 2s ease-in-out infinite; } - @-webkit-keyframes fade { from { opacity: 0 } to { opacity: 1 } } - @-moz-keyframes fade { from { opacity: 0 } to { opacity: 1 } } - @keyframes fade { from { opacity: 0 } to { opacity: 1 } } + @-webkit-keyframes fade { from { opacity: 1 } 50% { opacity: 0 } to { opacity: 1 } } + @-moz-keyframes fade { from { opacity: 1 } 50% { opacity: 0 } to { opacity: 1 } } + @keyframes fade { from { opacity: 1 } 50% { opacity: 0 } to { opacity: 1 } } .organization.icon { height: 13px; @@ -1355,7 +1358,6 @@ ol.tabs li { } .icon-switch:hover .organization.icon, - .task.active .organization.icon, .white.organization.icon { background-position: 0 -132px; } @@ -1367,7 +1369,6 @@ ol.tabs li { } .icon-switch:hover .user.icon, - .task.active .user.icon, .white.user.icon { background-position: -15px -132px; } @@ -1379,7 +1380,6 @@ ol.tabs li { } .icon-switch:hover .note.icon, - .task.active .note.icon, .white.note.icon { background-position: -30px -132px; } @@ -1391,7 +1391,6 @@ ol.tabs li { } .icon-switch:hover .pen.icon, - .task.active .pen.icon, .white.pen.icon { background-position: -45px -132px; } @@ -1402,7 +1401,6 @@ ol.tabs li { background-position: -60px -118px; } .icon-switch:hover .important.icon, - .task.active .important.icon, .white.important.icon { background-position: -60px -132px; } @@ -1413,7 +1411,6 @@ ol.tabs li { background-position: -75px -118px; } .icon-switch:hover .tools.icon, - .task.active .tools.icon, .white.tools.icon { background-position: -75px -132px; } @@ -1424,7 +1421,6 @@ ol.tabs li { background-position: -90px -118px; } .icon-switch:hover .clock.icon, - .task.active .clock.icon, .white.clock.icon { background-position: -90px -132px; } @@ -1432,10 +1428,37 @@ ol.tabs li { .team.icon { height: 13px; width: 20px; - background-position: -105px -118px; + background-position: -104px -118px; } .white.team.icon { - background-position: -105px -132px; + background-position: -104px -132px; + } + + .cloud.icon { + height: 12px; + width: 15px; + background-position: -125px -119px; + } + .white.cloud.icon { + background-position: -125px -133px; + } + + .package.icon { + height: 16px; + width: 15px; + background-position: -141px -112px; + } + .white.package.icon { + background-position: -141px -129px; + } + + .list.icon { + height: 14px; + width: 15px; + background-position: -157px -116px; + } + .white.list.icon { + background-position: -157px -131px; } .channel.icon { @@ -2074,6 +2097,8 @@ footer { padding: 10px 15px 7px 0; position: relative; @extend .u-clickable; + @extend .horizontal; + @extend .center; } .tasks-navigation .task { @@ -2254,6 +2279,7 @@ footer { .search.focused .logo { opacity: 0; + z-index: -1; } .search .logo { @@ -2707,18 +2733,23 @@ footer { } .popover--notifications { + min-height: 100px; @extend .zIndex-5; - + &.is-visible { @extend .vertical; } + .arrow { + top: 23px !important; + } + .popover-content { @extend .flex; } &.is-overflowing .popover-notificationsHeader { - box-shadow: + box-shadow: 0 1px hsla(240,4%,95%,.5), 0 2px hsla(240,4%,95%,.2); } @@ -2729,7 +2760,7 @@ footer { @extend .end; padding-bottom: 22px; margin: 21px 17px 0; - + .popover-title { @extend h1; padding: 0; @@ -4160,11 +4191,14 @@ footer { display: block; padding-left: 40px; position: absolute; - overflow: auto; - background: hsla(210,17%,98%,.55); + bottom: auto; + min-height: 100vh; .modal-backdrop { - display: none; + position: absolute; + background: hsla(210,17%,93%,.55); + height: 100% !important; + opacity: 1; } .modal-dialog { diff --git a/app/controllers/tickets_controller.rb b/app/controllers/tickets_controller.rb index b8fc5b27b..10e134267 100644 --- a/app/controllers/tickets_controller.rb +++ b/app/controllers/tickets_controller.rb @@ -279,8 +279,10 @@ class TicketsController < ApplicationController :assets => assets, :links => link_list, :tags => tags, - :form_meta => attributes_to_change, - :edit_form => attributes_to_change, + :form_meta => { + :filter => attributes_to_change[:filter], + :dependencies => attributes_to_change[:dependencies], + } } end diff --git a/app/models/observer/ticket/notification/background_job.rb b/app/models/observer/ticket/notification/background_job.rb index cafc133fd..49bc28538 100644 --- a/app/models/observer/ticket/notification/background_job.rb +++ b/app/models/observer/ticket/notification/background_job.rb @@ -69,7 +69,7 @@ class Observer::Ticket::Notification::BackgroundJob :object => 'Ticket', :o_id => ticket.id, :seen => false, - :created_by_id => ticket.created_by_id || 1, + :created_by_id => ticket.updated_by_id || 1, :user_id => user.id, ) @@ -292,7 +292,7 @@ State: i18n(#{ticket.state.name.text2html})

    ' end if user.preferences[:locale] =~ /^de/i - subject = 'Ticket aktualisiert (#{ticket.title.text2html})' + subject = 'Ticket aktualisiert (#{ticket.title})' body = '
    Hallo #{recipient.firstname.text2html},

    @@ -309,7 +309,7 @@ Ticket (#{ticket.title.text2html}) wurde von "#{ticket.updated_by.fullname.te
    ' else - subject = 'Updated Ticket (#{ticket.title.text2html})' + subject = 'Updated Ticket (#{ticket.title})' body = '
    Hi #{recipient.firstname.text2html},

    diff --git a/app/models/ticket/screen_options.rb b/app/models/ticket/screen_options.rb index fb435ebab..008cf8289 100644 --- a/app/models/ticket/screen_options.rb +++ b/app/models/ticket/screen_options.rb @@ -32,12 +32,12 @@ list attributes returns result = { - :type_id => type_ids, - :state_id => state_ids, - :priority_id => priority_ids, - :owner_id => owner_ids, - :group_id => group_ids, - :group_id__owner_id => groups_users, + :type_id => type_ids, + :state_id => state_ids, + :priority_id => priority_ids, + :owner_id => owner_ids, + :group_id => group_ids, + :group_id__owner_id => groups_users, } =end @@ -103,7 +103,7 @@ returns agents[ user.id ] = 1 } - dependencies = { :group_id => { '' => [] } } + dependencies = { :group_id => { '' => { :owner_id => [] } } } Group.where( :active => true ).each { |group| assets = group.assets(assets) dependencies[:group_id][group.id] = { :owner_id => [] } @@ -115,9 +115,9 @@ returns } return { - :assets => assets, - :filter => filter, - :dependencies => dependencies, + :assets => assets, + :filter => filter, + :dependencies => dependencies, } end @@ -126,8 +126,8 @@ returns list tickets by customer groupd in state categroie open and closed result = Ticket::ScreenOptions.list_by_customer( - :customer_id => 123, - :limit => 15, # optional, default 15 + :customer_id => 123, + :limit => 15, # optional, default 15 ) returns @@ -147,13 +147,13 @@ returns # get tickets tickets_open = Ticket.where( - :customer_id => data[:customer_id], - :state_id => state_list_open + :customer_id => data[:customer_id], + :state_id => state_list_open ).limit( data[:limit] || 15 ).order('created_at DESC') tickets_closed = Ticket.where( - :customer_id => data[:customer_id], - :state_id => state_list_closed + :customer_id => data[:customer_id], + :state_id => state_list_closed ).limit( data[:limit] || 15 ).order('created_at DESC') return { @@ -162,4 +162,4 @@ returns } end -end +end \ No newline at end of file diff --git a/public/assets/images/sprite.svg b/public/assets/images/sprite.svg index dae98acea..1afd254df 100644 --- a/public/assets/images/sprite.svg +++ b/public/assets/images/sprite.svg @@ -1,26 +1,46 @@ - + - Slice 2 + Slice 1 Created with Sketch. + + - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + - + - + @@ -28,7 +48,7 @@ - + @@ -40,22 +60,22 @@ - + - + - - - + + + @@ -126,39 +146,45 @@ - - - + + + - - + + - + + + + + + + - - - + + + - - - + + + - - + + @@ -170,7 +196,7 @@ - + diff --git a/test/browser/agent_ticket_actions_level5_test.rb b/test/browser/agent_ticket_actions_level5_test.rb new file mode 100644 index 000000000..0fa2cb90b --- /dev/null +++ b/test/browser/agent_ticket_actions_level5_test.rb @@ -0,0 +1,362 @@ +# encoding: utf-8 +require 'browser_test_helper' + +class AgentTicketActionLevel5Test < TestCase + def test_agent_signature_check + suffix = rand(99999999999999999).to_s + signature_name1 = 'sig name 1 äöüß ' + suffix + signature_body1 = "--\nsig body 1 äöüß " + suffix + signature_name2 = 'sig name 2 äöüß ' + suffix + signature_body2 = "--\nsig body 2 äöüß " + suffix + group_name1 = "group name 1 " + suffix + group_name2 = "group name 2 " + suffix + group_name3 = "group name 3 " + suffix + + tests = [ + { + :name => 'create groups and signatures', + :action => [ + + { + :execute => 'close_all_tasks', + }, + + # create signatures + { + :execute => 'create_signature', + :name => signature_name1, + :body => signature_body1, + }, + { + :execute => 'create_signature', + :name => signature_name2, + :body => signature_body2, + }, + + # create groups + { + :execute => 'create_group', + :name => group_name1, + :signature => signature_name1, + :member => [ + 'master@example.com' + ], + }, + { + :execute => 'create_group', + :name => group_name2, + :signature => signature_name2, + :member => [ + 'master@example.com' + ], + }, + { + :execute => 'create_group', + :name => group_name3, + :member => [ + 'master@example.com' + ], + }, + ], + }, + { + :name => 'check signature in new ticket', + :action => [ + + # reload instances to get new group permissions + { + :execute => 'reload', + }, + + { + :execute => 'create_ticket', + :group => 'Users', + :subject => 'some subject 4 - 123äöü', + :body => 'some body 4 - 123äöü', + :do_not_submit => true, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # select group + { + :execute => 'select', + :css => '.active [name="group_id"]', + :value => group_name1, + }, + + # select group + { + :execute => 'select', + :css => '.active [name="group_id"]', + :value => group_name1, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + # select create channel + { + :execute => 'click', + :css => '.active [data-type="email-out"]', + }, + + # select group + { + :execute => 'select', + :css => '.active select[name="group_id"]', + :value => group_name1, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => true, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + # select group + { + :execute => 'select', + :css => '.active select[name="group_id"]', + :value => group_name2, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => true, + }, + + # select group + { + :execute => 'select', + :css => '.active select[name="group_id"]', + :value => group_name3, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + + # select group + { + :execute => 'select', + :css => '.active select[name="group_id"]', + :value => group_name1, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => true, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + # select create channel + { + :execute => 'click', + :css => '.active [data-type="phone-out"]', + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + ], + }, + { + :name => 'check signature in zoom ticket', + :action => [ + + { + :execute => 'create_ticket', + :group => group_name1, + :subject => 'some subject 5 - 123äöü', + :body => 'some body 5 - 123äöü', + }, + { + :execute => 'wait', + :value => 3, + }, + + # execute reply + { + :execute => 'click', + :css => '.active [data-type="reply"]', + }, + + # check if signature exists + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => true, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + # discard changes + { + :execute => 'click', + :css => '.active .js-reset', + }, + { + :execute => 'wait', + :value => 3, + }, + + # check if signature exists + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + ], + }, + ] + browser_signle_test_with_login(tests, { :username => 'master@example.com' }) + end +end \ No newline at end of file diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb index 271a741f9..4be4ad056 100644 --- a/test/browser_test_helper.rb +++ b/test/browser_test_helper.rb @@ -30,8 +30,12 @@ class TestCase < Test::Unit::TestCase end caps = Selenium::WebDriver::Remote::Capabilities.send( browser ) - caps.platform = ENV['BROWSER_OS'] || 'Windows 2008' - caps.version = ENV['BROWSER_VERSION'] || '8' + if ENV['BROWSER_OS'] + caps.platform = ENV['BROWSER_OS'] + end + if ENV['BROWSER_VERSION'] + caps.version = ENV['BROWSER_VERSION'] + end local_browser = Selenium::WebDriver.for( :remote, :url => ENV['REMOTE_URL'], @@ -257,18 +261,22 @@ class TestCase < Test::Unit::TestCase if action[:timeout] timeout = action[:timeout] end - loops = (timeout / 2).to_i + loops = (timeout).to_i text = '' (1..loops).each { |loop| element = instance.find_elements( { :css => action[:area] } )[0] if element #&& element.displayed? - text = element.text - if text =~ /#{action[:value]}/i - assert( true, "(#{test[:name]}) '#{action[:value]}' found in '#{text}'" ) - return + begin + text = element.text + if text =~ /#{action[:value]}/i + assert( true, "(#{test[:name]}) '#{action[:value]}' found in '#{text}'" ) + return + end + rescue + # just try again end end - sleep 2 + sleep 1 } assert( false, "(#{test[:name]}) '#{action[:value]}' found in '#{text}'" ) return @@ -291,7 +299,6 @@ class TestCase < Test::Unit::TestCase assert( false, "(#{test[:name]} / #{test[:area]}) still exsists" ) return elsif action[:execute] == 'create_user' - instance.find_elements( { :css => 'a[href="#manage"]' } )[0].click instance.find_elements( { :css => 'a[href="#manage/users"]' } )[0].click sleep 2 @@ -329,6 +336,82 @@ class TestCase < Test::Unit::TestCase assert( true, "(#{test[:name]}) user creation failed" ) return + elsif action[:execute] == 'create_signature' + instance.find_elements( { :css => 'a[href="#manage"]' } )[0].click + instance.find_elements( { :css => 'a[href="#channels/email"]' } )[0].click + instance.find_elements( { :css => 'a[href="#c-signature"]' } )[0].click + sleep 8 + instance.find_elements( { :css => '#content #c-signature a[data-type="new"]' } )[0].click + sleep 2 + element = instance.find_elements( { :css => '.modal input[name=name]' } )[0] + element.clear + element.send_keys( action[:name] ) + element = instance.find_elements( { :css => '.modal textarea[name=body]' } )[0] + element.clear + element.send_keys( action[:body] ) + instance.find_elements( { :css => '.modal button.js-submit' } )[0].click + (1..12).each {|loop| + element = instance.find_elements( { :css => 'body' } )[0] + text = element.text + if text =~ /#{Regexp.quote(action[:name])}/ + assert( true, "(#{test[:name]}) signature created" ) + return + end + sleep 1 + } + assert( true, "(#{test[:name]}) signature creation failed" ) + return + + elsif action[:execute] == 'create_group' + instance.find_elements( { :css => 'a[href="#manage"]' } )[0].click + instance.find_elements( { :css => 'a[href="#manage/groups"]' } )[0].click + sleep 2 + instance.find_elements( { :css => 'a[data-type="new"]' } )[0].click + sleep 2 + element = instance.find_elements( { :css => '.modal input[name=name]' } )[0] + element.clear + element.send_keys( action[:name] ) + element = instance.find_elements( { :css => '.modal select[name="email_address_id"]' } )[0] + dropdown = Selenium::WebDriver::Support::Select.new(element) + dropdown.select_by( :index, 1 ) + #dropdown.select_by( :text, action[:group]) + if action[:signature] + element = instance.find_elements( { :css => '.modal select[name="signature_id"]' } )[0] + dropdown = Selenium::WebDriver::Support::Select.new(element) + dropdown.select_by( :text, action[:signature]) + end + instance.find_elements( { :css => '.modal button.js-submit' } )[0].click + (1..12).each {|loop| + element = instance.find_elements( { :css => 'body' } )[0] + text = element.text + if text =~ /#{Regexp.quote(action[:name])}/ + assert( true, "(#{test[:name]}) group created" ) + + # add member + if action[:member] + action[:member].each {|login| + instance.find_elements( { :css => 'a[href="#manage"]' } )[0].click + instance.find_elements( { :css => 'a[href="#manage/users"]' } )[0].click + sleep 2 + element = instance.find_elements( { :css => '#content [name="search"]' } )[0] + element.clear + element.send_keys( login ) + sleep 2 + #instance.find_elements( { :css => '#content table [data-id]' } )[0].click + instance.execute_script( '$("#content table [data-id] td").first().click()' ) + sleep 2 + #instance.find_elements( { :css => 'label:contains(" ' + action[:name] + '")' } )[0].click + instance.execute_script( '$(\'label:contains(" ' + action[:name] + '")\').first().click()' ) + instance.find_elements( { :css => '.modal button.js-submit' } )[0].click + } + end + return + end + sleep 1 + } + assert( true, "(#{test[:name]}) group creation failed" ) + return + elsif action[:execute] == 'verify_task_attributes' if action[:title] text = instance.find_elements( { :css => '.tasks .active' } )[0].text.strip @@ -389,6 +472,11 @@ class TestCase < Test::Unit::TestCase return end sleep 2 + + # check count of agents, should be only 1 / - selection on init screen + count = instance.find_elements( { :css => '.active .newTicket select[name="owner_id"] option' } ).count + assert_equal( 1, count, 'check if owner selection is empty per default' ) + if action[:group] element = instance.find_elements( { :css => '.active .newTicket select[name="group_id"]' } )[0] dropdown = Selenium::WebDriver::Support::Select.new(element) diff --git a/test/unit/online_notifiaction_test.rb b/test/unit/online_notifiaction_test.rb new file mode 100644 index 000000000..53d2231e7 --- /dev/null +++ b/test/unit/online_notifiaction_test.rb @@ -0,0 +1,142 @@ +# encoding: utf-8 +require 'test_helper' + +class OnlineNotificationTest < ActiveSupport::TestCase + role = Role.lookup( :name => 'Agent' ) + group = Group.lookup( :name => 'Users' ) + agent_user1 = User.create_or_update( + :login => 'agent_online_notify1', + :firstname => 'Bob', + :lastname => 'Smith', + :email => 'agent_online_notify1@example.com', + :password => 'some_pass', + :active => true, + :role_ids => [role.id], + :group_ids => [group.id], + :updated_by_id => 1, + :created_by_id => 1 + ) + agent_user2 = User.create_or_update( + :login => 'agent_online_notify2', + :firstname => 'Bob', + :lastname => 'Smith', + :email => 'agent_online_notify2@example.com', + :password => 'some_pass', + :active => true, + :role_ids => [role.id], + :group_ids => [group.id], + :updated_by_id => 1, + :created_by_id => 1 + ) + customer_user = User.lookup( :login => 'nicole.braun@zammad.org' ) + + test 'ticket notifiaction' do + tests = [ + + # test 1 + { + :create => { + :ticket => { + :group_id => Group.lookup( :name => 'Users' ).id, + :customer_id => customer_user.id, + :owner_id => User.lookup( :login => '-' ).id, + :title => 'Unit Test 1 (äöüß)!', + :state_id => Ticket::State.lookup( :name => 'new' ).id, + :priority_id => Ticket::Priority.lookup( :name => '2 normal' ).id, + :updated_by_id => agent_user1.id, + :created_by_id => agent_user1.id, + }, + :article => { + :updated_by_id => agent_user1.id, + :created_by_id => agent_user1.id, + :type_id => Ticket::Article::Type.lookup( :name => 'phone' ).id, + :sender_id => Ticket::Article::Sender.lookup( :name => 'Customer' ).id, + :from => 'Unit Test ', + :body => 'Unit Test 123', + :internal => false + }, + }, + :update => { + :ticket => { + :title => 'Unit Test 1 (äöüß) - update!', + :state_id => Ticket::State.lookup( :name => 'open' ).id, + :priority_id => Ticket::Priority.lookup( :name => '1 low' ).id, + :updated_by_id => customer_user.id, + }, + }, + :check => [ + { + :type => 'create', + :object => 'Ticket', + :created_by_id => agent_user1.id, + }, + { + :type => 'update', + :object => 'Ticket', + :created_by_id => customer_user.id, + }, + ] + }, + ] + tickets = [] + tests.each { |test| + + ticket = Ticket.create( test[:create][:ticket] ) + test[:check][0][:o_id] = ticket.id + test[:check][1][:o_id] = ticket.id + + test[:create][:article][:ticket_id] = ticket.id + article = Ticket::Article.create( test[:create][:article] ) + + assert_equal( ticket.class.to_s, 'Ticket' ) + + # execute ticket events + Observer::Ticket::Notification.transaction + #puts Delayed::Job.all.inspect + Delayed::Worker.new.work_off + + # update ticket + if test[:update][:ticket] + ticket.update_attributes( test[:update][:ticket] ) + end + + # execute ticket events + Observer::Ticket::Notification.transaction + #puts Delayed::Job.all.inspect + Delayed::Worker.new.work_off + + # remember ticket + tickets.push ticket + + # check online notifications + notification_check( OnlineNotification.list(agent_user2, 10), test[:check] ) + } + + # delete tickets + tickets.each { |ticket| + ticket_id = ticket.id + ticket.destroy + found = Ticket.where( :id => ticket_id ).first + assert( !found, "Ticket destroyed") + } + end + + def notification_check( onine_notifications, checks ) + checks.each { |check_item| + hit = false + onine_notifications.each {|onine_notification| + if onine_notification['o_id'] == check_item[:o_id] + if onine_notification['object'] == check_item[:object] + if onine_notification['type'] == check_item[:type] + if onine_notification['created_by_id'] == check_item[:created_by_id] + hit = true + end + end + end + end + } + assert( hit, "online notification exists #{ check_item.inspect }" ) + } + end + +end \ No newline at end of file diff --git a/test/unit/ticket_notification_test.rb b/test/unit/ticket_notification_test.rb index 27018e0f2..ad3134599 100644 --- a/test/unit/ticket_notification_test.rb +++ b/test/unit/ticket_notification_test.rb @@ -364,7 +364,7 @@ class TicketNotificationTest < ActiveSupport::TestCase # create ticket in group ticket1 = Ticket.create( - :title => 'some notification template test 1', + :title => 'some notification template test 1 Bobs\'s resumé', :group => Group.lookup( :name => 'Users'), :customer => customer, :state => Ticket::State.lookup( :name => 'new' ), @@ -413,6 +413,16 @@ class TicketNotificationTest < ActiveSupport::TestCase assert_match( /updated/i, template[:subject] ) # en notification + subject = NotificationFactory.build( + :locale => agent2.preferences[:locale], + :string => template[:subject], + :objects => { + :ticket => ticket1, + :article => article, + :recipient => agent2, + } + ) + assert_match( /Bobs's resumé/, subject ) body = NotificationFactory.build( :locale => agent2.preferences[:locale], :string => template[:body], @@ -437,6 +447,16 @@ class TicketNotificationTest < ActiveSupport::TestCase assert_match( /aktualis/, template[:subject] ) # de notification + subject = NotificationFactory.build( + :locale => agent1.preferences[:locale], + :string => template[:subject], + :objects => { + :ticket => ticket1, + :article => article, + :recipient => agent2, + } + ) + assert_match( /Bobs's resumé/, subject ) body = NotificationFactory.build( :locale => agent1.preferences[:locale], :string => template[:body],