<commit_before>/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* In accordance with the terms of section 7 (7. Additional Terms.) of
* the GNU Affero GPL v3, the copyright holders add the following
* Additional permissions:
* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU Affero GPL v3
* licence, when you create a Related Module, this Related Module is
* not considered as a part of the work and may be distributed under the
* license agreement of your choice.
* A "Related Module" means a set of sources files including their
* documentation that, without modification of the Source Code, enables
* supplementary functions or services in addition to those offered by
* the Software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/agpl.html>.
*
*************************************************************************************
*/

package com.normation.rudder.web.snippet.configuration

import com.normation.rudder.web.components.popup.CreateDirectivePopup
import com.normation.rudder.web.model.JsTreeNode
import com.normation.rudder.domain.policies.{DirectiveId, Directive}
import com.normation.cfclerk.domain.{Technique,TechniqueId}
import com.normation.cfclerk.services.TechniqueRepository
import com.normation.utils.StringUuidGenerator
import com.normation.rudder.web.components.{DirectiveEditForm,DateFormaterService}
import com.normation.rudder.repository._
import scala.xml._
import net.liftweb.common._
import Box._
import net.liftweb.http._
import net.liftweb.http.js._
import JsCmds._
import JE._
import net.liftweb.util.Helpers._
import net.liftweb.util._
import com.normation.cfclerk.domain.TechniqueVersion
import com.normation.rudder.web.services.JsTreeUtilService
import bootstrap.liftweb.RudderConfig
import com.normation.rudder.domain.workflows.ChangeRequestId
import com.normation.rudder.repository.FullActiveTechniqueCategory
import com.normation.rudder.repository.FullActiveTechnique
import com.normation.cfclerk.domain.TechniqueId
import com.normation.rudder.web.services.DisplayDirectiveTree
import com.normation.rudder.web.model.CurrentUser
import com.normation.rudder.authorization.AuthzToRights
import com.normation.rudder.authorization.NoRights
import org.joda.time.DateTime

/**
 * Snippet for managing the System and Active Technique libraries.
 *
 * It allow to see what Techniques are available in the
 * system library, choose and configure which one to use in
 * the user private library.
 *
 * Techniques are classify by categories in a tree.
 *
 */
class DirectiveManagement extends DispatchSnippet with Loggable {
  import DirectiveManagement._

  val techniqueRepository = RudderConfig.techniqueRepository
  val getDirectiveLib     = () => RudderConfig.roDirectiveRepository.getFullDirectiveLibrary
  val getRules            = () => RudderConfig.roRuleRepository.getAll()
  val uuidGen             = RudderConfig.stringUuidGenerator
  val treeUtilService     = RudderConfig.jsTreeUtilService

  def dispatch = {
    RudderConfig.configService.rudder_workflow_enabled match {
      case Full(workflowEnabled) =>
        {
          case "head" => { _ => head(workflowEnabled) }
          case "userLibrary" => { _ => userLibrary(workflowEnabled) }
          case "showDirectiveDetails" => { _ => initDirectiveDetails(workflowEnabled) }
          case "techniqueDetails" => { xml =>
            techniqueDetails = initTechniqueDetails(workflowEnabled)
            techniqueDetails.apply(xml)
          }
        }
      case eb:EmptyBox =>
        val e = eb ?~! "Error when trying to read Rudder configuration for workflow activation"
        logger.error(s"Error when displaying Directives : ${e.messageChain}")

        {
          case "userLibrary" => { _ => <div class="error">{e.msg}</div> }
          case _ => { _ => NodeSeq.Empty }
        }
    }
  }

  //must be initialized on first call of "techniqueDetails"
  private[this] var techniqueDetails: MemoizeTransform = null

  //the current DirectiveEditForm component
  val currentDirectiveSettingForm = new LocalSnippet[DirectiveEditForm]

  //we specify here what is the technique we are working on
  //technique version must be in the map of techniques in the fullActiveTechnique
  var currentTechnique = Option.empty[(FullActiveTechnique, TechniqueVersion)]

  //the state of the directive library.
  //must be reloaded "updateDirectiveLibrary()"
  //when information change (directive added/removed/modified, etc)
  var directiveLibrary = getDirectiveLib()
  var rules = getRules()

  private[this] val directiveId: Box[String] = S.param("directiveId")


  /**
   * Head information (JsTree dependencies,...)
   */
  def head(workflowEnabled: Boolean) : NodeSeq = {
    DirectiveEditForm.staticInit ++
    (
      <head>
        <script type="text/javascript" src="/javascript/jstree/jquery.jstree.js" id="jstree">
        </script>
        <script type="text/javascript" src="/javascript/rudder/tree.js" id="tree">
        </script>
        {Script(OnLoad(parseJsArg(workflowEnabled)))}
      </head>
    )
  }

  /**
   * If a query is passed as argument, try to dejoniffy-it, in a best effort
   * way - just don't take of errors.
   *
   * We want to look for #{ "directiveId":"XXXXXXXXXXXX" }
   */
  private[this] def parseJsArg(workflowEnabled: Boolean): JsCmd = {

    def displayDetails(directiveId:String) = displayDirectiveDetails(DirectiveId(directiveId), workflowEnabled)

    JsRaw("""
        var directiveId = null;
        try {
          directiveId = JSON.parse(window.location.hash.substring(1)).directiveId ;
        } catch(e) {
          directiveId = null;
        }
        if( directiveId != null && directiveId.length > 0) {
          %s;
        }
    """.format(SHtml.ajaxCall(JsVar("directiveId"), displayDetails _ )._2.toJsCmd)
    )
  }

  /**
   * Almost same as Technique/activeTechniquesTree
   * TODO : factor out that part
   */
  def userLibrary(workflowEnabled: Boolean): NodeSeq = {
    (
      <div id={htmlId_activeTechniquesTree} class="nodisplay">{
          (directiveLibrary,rules) match {
            case (Full(activeTechLib), Full(allRules)) =>
              val usedDirectives = allRules.flatMap { case r =>
                  r.directiveIds.map( id => (id -> r.id))
                }.groupBy( _._1 ).mapValues( _.size).toSeq

              <ul>{
                DisplayDirectiveTree.displayTree(activeTechLib, usedDirectives, None, Some(onClickActiveTechnique), Some(onClickDirective(workflowEnabled)))
              }</ul>
            case (x, y) =>

              (x :: y :: Nil).foreach {
                case eb: EmptyBox =>
                  val f = eb ?~! "Error when trying to get the root category of Active Techniques"
                  logger.error(f.messageChain)
                  f.rootExceptionCause.foreach { ex =>
                    logger.error("Exception causing the error was:" , ex)
                  }

                case _ => //
              }

              <span class="error">An error occured when trying to get information from the database. Please contact your administrator of retry latter.</span>
          }
      }</div>
    ) ++ Script(OnLoad(buildJsTree()))
  }


  private[this] def buildJsTree() : JsCmd = {

    def isDirectiveIdValid(directiveId: String): JsCmd = {
      directiveLibrary.flatMap( _.allDirectives.get(DirectiveId(directiveId))) match {
        case Full((activeTechnique, directive)) =>
          JsRaw(""" buildDirectiveTree('#%s', '%s', '%s') """
            .format(htmlId_activeTechniquesTree, "jsTree-" + directive.id.value, S.contextPath))
        case e:EmptyBox =>
          JsRaw(""" buildDirectiveTree('#%s', '', '%s') """.format(htmlId_activeTechniquesTree, S.contextPath))
      }
    }

    JsRaw("""
        var directiveId = null;
        try {
          directiveId = JSON.parse(window.location.hash.substring(1)).directiveId ;
        } catch(e) {
          directiveId = null;
        }

        %s;

    """.format(SHtml.ajaxCall(JsVar("directiveId"), isDirectiveIdValid _ )._2.toJsCmd,
         htmlId_activeTechniquesTree))
  }
<commit_msg>Fixes #4223: Add directive tooltip after update<commit_after>/*
*************************************************************************************
* Copyright 2011 Normation SAS
*************************************************************************************
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* In accordance with the terms of section 7 (7. Additional Terms.) of
* the GNU Affero GPL v3, the copyright holders add the following
* Additional permissions:
* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU Affero GPL v3
* licence, when you create a Related Module, this Related Module is
* not considered as a part of the work and may be distributed under the
* license agreement of your choice.
* A "Related Module" means a set of sources files including their
* documentation that, without modification of the Source Code, enables
* supplementary functions or services in addition to those offered by
* the Software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/agpl.html>.
*
*************************************************************************************
*/

package com.normation.rudder.web.snippet.configuration

import com.normation.rudder.web.components.popup.CreateDirectivePopup
import com.normation.rudder.web.model.JsTreeNode
import com.normation.rudder.domain.policies.{DirectiveId, Directive}
import com.normation.cfclerk.domain.{Technique,TechniqueId}
import com.normation.cfclerk.services.TechniqueRepository
import com.normation.utils.StringUuidGenerator
import com.normation.rudder.web.components.{DirectiveEditForm,DateFormaterService}
import com.normation.rudder.repository._
import scala.xml._
import net.liftweb.common._
import Box._
import net.liftweb.http._
import net.liftweb.http.js._
import JsCmds._
import JE._
import net.liftweb.util.Helpers._
import net.liftweb.util._
import com.normation.cfclerk.domain.TechniqueVersion
import com.normation.rudder.web.services.JsTreeUtilService
import bootstrap.liftweb.RudderConfig
import com.normation.rudder.domain.workflows.ChangeRequestId
import com.normation.rudder.repository.FullActiveTechniqueCategory
import com.normation.rudder.repository.FullActiveTechnique
import com.normation.cfclerk.domain.TechniqueId
import com.normation.rudder.web.services.DisplayDirectiveTree
import com.normation.rudder.web.model.CurrentUser
import com.normation.rudder.authorization.AuthzToRights
import com.normation.rudder.authorization.NoRights
import org.joda.time.DateTime

/**
 * Snippet for managing the System and Active Technique libraries.
 *
 * It allow to see what Techniques are available in the
 * system library, choose and configure which one to use in
 * the user private library.
 *
 * Techniques are classify by categories in a tree.
 *
 */
class DirectiveManagement extends DispatchSnippet with Loggable {
  import DirectiveManagement._

  val techniqueRepository = RudderConfig.techniqueRepository
  val getDirectiveLib     = () => RudderConfig.roDirectiveRepository.getFullDirectiveLibrary
  val getRules            = () => RudderConfig.roRuleRepository.getAll()
  val uuidGen             = RudderConfig.stringUuidGenerator
  val treeUtilService     = RudderConfig.jsTreeUtilService

  def dispatch = {
    RudderConfig.configService.rudder_workflow_enabled match {
      case Full(workflowEnabled) =>
        {
          case "head" => { _ => head(workflowEnabled) }
          case "userLibrary" => { _ => userLibrary(workflowEnabled) }
          case "showDirectiveDetails" => { _ => initDirectiveDetails(workflowEnabled) }
          case "techniqueDetails" => { xml =>
            techniqueDetails = initTechniqueDetails(workflowEnabled)
            techniqueDetails.apply(xml)
          }
        }
      case eb:EmptyBox =>
        val e = eb ?~! "Error when trying to read Rudder configuration for workflow activation"
        logger.error(s"Error when displaying Directives : ${e.messageChain}")

        {
          case "userLibrary" => { _ => <div class="error">{e.msg}</div> }
          case _ => { _ => NodeSeq.Empty }
        }
    }
  }

  //must be initialized on first call of "techniqueDetails"
  private[this] var techniqueDetails: MemoizeTransform = null

  //the current DirectiveEditForm component
  val currentDirectiveSettingForm = new LocalSnippet[DirectiveEditForm]

  //we specify here what is the technique we are working on
  //technique version must be in the map of techniques in the fullActiveTechnique
  var currentTechnique = Option.empty[(FullActiveTechnique, TechniqueVersion)]

  //the state of the directive library.
  //must be reloaded "updateDirectiveLibrary()"
  //when information change (directive added/removed/modified, etc)
  var directiveLibrary = getDirectiveLib()
  var rules = getRules()

  private[this] val directiveId: Box[String] = S.param("directiveId")


  /**
   * Head information (JsTree dependencies,...)
   */
  def head(workflowEnabled: Boolean) : NodeSeq = {
    DirectiveEditForm.staticInit ++
    (
      <head>
        <script type="text/javascript" src="/javascript/jstree/jquery.jstree.js" id="jstree">
        </script>
        <script type="text/javascript" src="/javascript/rudder/tree.js" id="tree">
        </script>
        {Script(OnLoad(parseJsArg(workflowEnabled)))}
      </head>
    )
  }

  /**
   * If a query is passed as argument, try to dejoniffy-it, in a best effort
   * way - just don't take of errors.
   *
   * We want to look for #{ "directiveId":"XXXXXXXXXXXX" }
   */
  private[this] def parseJsArg(workflowEnabled: Boolean): JsCmd = {

    def displayDetails(directiveId:String) = displayDirectiveDetails(DirectiveId(directiveId), workflowEnabled)

    JsRaw("""
        var directiveId = null;
        try {
          directiveId = JSON.parse(window.location.hash.substring(1)).directiveId ;
        } catch(e) {
          directiveId = null;
        }
        if( directiveId != null && directiveId.length > 0) {
          %s;
        }
    """.format(SHtml.ajaxCall(JsVar("directiveId"), displayDetails _ )._2.toJsCmd)
    )
  }

  /**
   * Almost same as Technique/activeTechniquesTree
   * TODO : factor out that part
   */
  def userLibrary(workflowEnabled: Boolean): NodeSeq = {
    (
      <div id={htmlId_activeTechniquesTree} class="nodisplay">{
          (directiveLibrary,rules) match {
            case (Full(activeTechLib), Full(allRules)) =>
              val usedDirectives = allRules.flatMap { case r =>
                  r.directiveIds.map( id => (id -> r.id))
                }.groupBy( _._1 ).mapValues( _.size).toSeq

              <ul>{
                DisplayDirectiveTree.displayTree(activeTechLib, usedDirectives, None, Some(onClickActiveTechnique), Some(onClickDirective(workflowEnabled)))
              }</ul>
            case (x, y) =>

              (x :: y :: Nil).foreach {
                case eb: EmptyBox =>
                  val f = eb ?~! "Error when trying to get the root category of Active Techniques"
                  logger.error(f.messageChain)
                  f.rootExceptionCause.foreach { ex =>
                    logger.error("Exception causing the error was:" , ex)
                  }

                case _ => //
              }

              <span class="error">An error occured when trying to get information from the database. Please contact your administrator of retry latter.</span>
          }
      }</div>
    ) ++ Script(OnLoad(buildJsTree()))
  }


  private[this] def buildJsTree() : JsCmd = {

    def isDirectiveIdValid(directiveId: String): JsCmd = {
      directiveLibrary.flatMap( _.allDirectives.get(DirectiveId(directiveId))) match {
        case Full((activeTechnique, directive)) =>
          JsRaw(""" buildDirectiveTree('#%s', '%s', '%s') """
            .format(htmlId_activeTechniquesTree, "jsTree-" + directive.id.value, S.contextPath))
        case e:EmptyBox =>
          JsRaw(""" buildDirectiveTree('#%s', '', '%s') """.format(htmlId_activeTechniquesTree, S.contextPath))
      }
    }

    JsRaw(s"""
        var directiveId = null;
        try {
          directiveId = JSON.parse(window.location.hash.substring(1)).directiveId ;
        } catch(e) {
          directiveId = null;
        }

        ${SHtml.ajaxCall(JsVar("directiveId"), isDirectiveIdValid _ )._2.toJsCmd};
        createTooltip();
    """)
  }

{'fraction_non_alphanumeric': 0.12597235435670714, 'fraction_numerical': 0.0023504393082992895, 'mean_word_length': 3.2874280230326294, 'pattern_counts': {'":': 2, '<': 33, '<?xml version=': 0, '>': 87, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3321730', 'n_tokens_mistral': 5089, 'n_tokens_neox': 4870, 'n_words': 1365}