<commit_before>import SPELLS from 'common/SPELLS';
import ISSUE_IMPORTANCE from 'Parser/Core/ISSUE_IMPORTANCE';
import CoreAbilities from 'Parser/Core/Modules/Abilities';

class Abilities extends CoreAbilities {
  spellbook() {
    const combatant = this.combatants.selected;
    return [
      // Riptide with EotE
      {
        spell: SPELLS.RIPTIDE,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        charges: 2,
        cooldown: 6,
        enabled: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id),
        timelineSortIndex: 11,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      // Riptide without EotE
      {
        spell: SPELLS.RIPTIDE,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        cooldown: 6,
        enabled: !(combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id)),
        timelineSortIndex: 11,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.30,
          averageIssueEfficiency: 0.50,
          recommendedEfficiency: 0.70,
        },
      },
      {
        spell: SPELLS.HEALING_STREAM_TOTEM_CAST,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        charges: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id) ? 2 : 1,
        timelineSortIndex: 18,
        enabled: !combatant.hasTalent(SPELLS.CLOUDBURST_TOTEM_TALENT.id),
        isOnGCD: true,
        cooldown: 30,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      {
        spell: SPELLS.ASTRAL_SHIFT,
        buffSpellId: SPELLS.ASTRAL_SHIFT.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        timelineSortIndex: 82,
        cooldown: 90,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
        },
      },
      {
        spell: SPELLS.HEALING_RAIN_CAST,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 10,
        timelineSortIndex: 17,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.30,
          averageIssueEfficiency: 0.50,
          recommendedEfficiency: 0.70,
        },
      },
      {
        spell: SPELLS.WELLSPRING_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 20,
        timelineSortIndex: 20,
        isOnGCD: true,
        enabled: combatant.lv100Talent === SPELLS.WELLSPRING_TALENT.id,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.30,
          averageIssueEfficiency: 0.50,
          recommendedEfficiency: 0.70,
        },
      },
      {
        spell: SPELLS.CLOUDBURST_TOTEM_TALENT,
        buffSpellId: SPELLS.CLOUDBURST_TOTEM_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        charges: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id) ? 2 : 1,
        cooldown: 30,
        timelineSortIndex: 16,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.CLOUDBURST_TOTEM_TALENT.id),
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      {
        spell: SPELLS.EARTHEN_SHIELD_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 60,
        timelineSortIndex: 20,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.EARTHEN_SHIELD_TOTEM_TALENT.id),
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      {
        spell: SPELLS.UNLEASH_LIFE_TALENT,
        buffSpellId: SPELLS.UNLEASH_LIFE_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 15,
        timelineSortIndex: 5,
        isOnGCD: true,
        enabled: combatant.lv15Talent === SPELLS.UNLEASH_LIFE_TALENT.id,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      {
        spell: SPELLS.ASCENDANCE_TALENT_RESTORATION,
        buffSpellId: SPELLS.ASCENDANCE_TALENT_RESTORATION.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
        enabled: combatant.lv100Talent === SPELLS.ASCENDANCE_TALENT_RESTORATION.id,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.2,
          averageIssueEfficiency: 0.5,
          recommendedEfficiency: 0.8,
        },
      },
      {
        spell: SPELLS.HEALING_TIDE_TOTEM_CAST,
        buffSpellId: SPELLS.HEALING_TIDE_TOTEM_CAST.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.2,
          averageIssueEfficiency: 0.5,
          recommendedEfficiency: 0.8,
        },
      },
      {
        spell: SPELLS.SPIRIT_LINK_TOTEM,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.2,
          averageIssueEfficiency: 0.4,
          recommendedEfficiency: 0.6,
        },
      },
      {
        spell: SPELLS.HEALING_WAVE,
        timelineSortIndex: 13,
        isOnGCD: true,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        castEfficiency: {
          casts: castCount => (castCount.casts || 0) - (castCount.healingTwHits || 0),
        },
      },
      {
        spell: SPELLS.HEALING_WAVE,
        name: `Tidal Waved ${SPELLS.HEALING_WAVE.name}`,
        timelineSortIndex: 13,
        isOnGCD: true,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        castEfficiency: {
          casts: castCount => castCount.healingTwHits || 0,
        },
      },
      {
        spell: SPELLS.HEALING_SURGE_RESTORATION,
        timelineSortIndex: 14,
        isOnGCD: true,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        castEfficiency: {
          casts: castCount => (castCount.casts || 0) - (castCount.healingTwHits || 0),
        },
      },
      {
        spell: SPELLS.HEALING_SURGE_RESTORATION,
        name: `Tidal Waved ${SPELLS.HEALING_SURGE_RESTORATION.name}`,
        timelineSortIndex: 14,
        isOnGCD: true,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        castEfficiency: {
          casts: castCount => castCount.healingTwHits || 0,
        },
      },
      {
        spell: SPELLS.CHAIN_HEAL,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        timelineSortIndex: 12,
        isOnGCD: true,
      },
      {
        spell: SPELLS.PURIFY_SPIRIT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        cooldown: 8,
        isOnGCD: true,
      },
      {
        spell: SPELLS.FLAME_SHOCK_RESTORATION,
        buffSpellId: SPELLS.FLAME_SHOCK_RESTORATION.id,
        category: Abilities.SPELL_CATEGORIES.HEALER_DAMAGING_SPELL,
        timelineSortIndex: 60,
        cooldown: 6,
        isOnGCD: true,
      },
      {
        spell: SPELLS.LAVA_BURST,
        buffSpellId: SPELLS.LAVA_SURGE.id,
        category: Abilities.SPELL_CATEGORIES.HEALER_DAMAGING_SPELL,
        charges: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id) ? 2 : 1,
        timelineSortIndex: 60,
        cooldown: 8,
        isOnGCD: true,
      },
      {
        spell: SPELLS.LIGHTNING_BOLT_RESTORATION,
        category: Abilities.SPELL_CATEGORIES.HEALER_DAMAGING_SPELL,
        isOnGCD: true,
        timelineSortIndex: 60,
      },
      {
        spell: SPELLS.CHAIN_LIGHTNING_RESTORATION,
        category: Abilities.SPELL_CATEGORIES.HEALER_DAMAGING_SPELL,
        isOnGCD: true,
        timelineSortIndex: 60,
      },
      {
        spell: SPELLS.GHOST_WOLF,
        buffSpellId: SPELLS.GHOST_WOLF.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
      },
      {
        spell: SPELLS.SPIRITWALKERS_GRACE,
        buffSpellId: SPELLS.SPIRITWALKERS_GRACE.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: combatant.hasTalent(SPELLS.GRACEFUL_SPIRIT_TALENT.id) ? 60 : 120,
        timelineSortIndex: 81,
      },
      {
        spell: SPELLS.EARTHBIND_TOTEM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
        cooldown: 30,
      },
      {
        spell: SPELLS.PURGE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
      },
      {
        spell: SPELLS.LIGHTNING_SURGE_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
        cooldown: 60,
      },
      {
        spell: SPELLS.WIND_RUSH_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.WIND_RUSH_TOTEM_TALENT.id),
        cooldown: 120,
      },
      {
        spell: SPELLS.EARTHGRAB_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.EARTHGRAB_TOTEM_TALENT.id),
        cooldown: 30,
      },
      {
        spell: SPELLS.ANCESTRAL_PROTECTION_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.ANCESTRAL_PROTECTION_TOTEM_TALENT.id),
        cooldown: 300,
      },
      {
        spell: SPELLS.REINCARNATION,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 1800,
      },
      {
        spell: SPELLS.WIND_SHEAR,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        cooldown: 12,
      },
      {
        spell: [SPELLS.HEX, SPELLS.HEX_RAPTOR, SPELLS.HEX_SNAKE, SPELLS.HEX_SPIDER, SPELLS.HEX_COCKROACH, SPELLS.HEX_SKELETAL],
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        isOnGCD: true,
        timelineSortIndex: 80,
        cooldown: 30,
      },
      {
        spell: SPELLS.EARTH_SHIELD_TALENT,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.EARTH_SHIELD_TALENT.id),
        timelineSortIndex: 80,
      },
      {
        spell: SPELLS.TREMOR_TOTEM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 60,
        isOnGCD: true,
        timelineSortIndex: 80,
      },
      {
        spell: SPELLS.DOWNPOUR_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 35, // CD changes depending on amount of effective targets hit (0 = 5s, 6 = 35s)
        isOnGCD: true,
        timelineSortIndex: 20,
        enabled: combatant.hasTalent(SPELLS.DOWNPOUR_TALENT.id),
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.20,
          averageIssueEfficiency: 0.40,
          recommendedEfficiency: 0.60,
        },
      },
      {
        spell: SPELLS.BERSERKING,
        buffSpellId: SPELLS.BERSERKING.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isUndetectable: true,
      },
    ];
  }
}

export default Abilities;
<commit_msg>remove code duplication<commit_after>import SPELLS from 'common/SPELLS';
import ISSUE_IMPORTANCE from 'Parser/Core/ISSUE_IMPORTANCE';
import CoreAbilities from 'Parser/Core/Modules/Abilities';

class Abilities extends CoreAbilities {
  spellbook() {
    const combatant = this.combatants.selected;
    return [
      {
        spell: SPELLS.RIPTIDE,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        charges: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id) ? 2 : 1,
        cooldown: 6,
        timelineSortIndex: 11,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id) ? 0.90 : 0.70,
        },
      },
      {
        spell: SPELLS.HEALING_STREAM_TOTEM_CAST,
        category: Abilities.SPELL_CATEGORIES.ROTATIONAL,
        charges: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id) ? 2 : 1,
        timelineSortIndex: 18,
        enabled: !combatant.hasTalent(SPELLS.CLOUDBURST_TOTEM_TALENT.id),
        isOnGCD: true,
        cooldown: 30,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      {
        spell: SPELLS.ASTRAL_SHIFT,
        buffSpellId: SPELLS.ASTRAL_SHIFT.id,
        category: Abilities.SPELL_CATEGORIES.DEFENSIVE,
        timelineSortIndex: 82,
        cooldown: 90,
        castEfficiency: {
          suggestion: true,
          recommendedEfficiency: 0.6,
          importance: ISSUE_IMPORTANCE.MINOR,
        },
      },
      {
        spell: SPELLS.HEALING_RAIN_CAST,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 10,
        timelineSortIndex: 17,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.30,
          averageIssueEfficiency: 0.50,
          recommendedEfficiency: 0.70,
        },
      },
      {
        spell: SPELLS.WELLSPRING_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 20,
        timelineSortIndex: 20,
        isOnGCD: true,
        enabled: combatant.lv100Talent === SPELLS.WELLSPRING_TALENT.id,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.30,
          averageIssueEfficiency: 0.50,
          recommendedEfficiency: 0.70,
        },
      },
      {
        spell: SPELLS.CLOUDBURST_TOTEM_TALENT,
        buffSpellId: SPELLS.CLOUDBURST_TOTEM_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        charges: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id) ? 2 : 1,
        cooldown: 30,
        timelineSortIndex: 16,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.CLOUDBURST_TOTEM_TALENT.id),
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      {
        spell: SPELLS.EARTHEN_SHIELD_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 60,
        timelineSortIndex: 20,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.EARTHEN_SHIELD_TOTEM_TALENT.id),
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      {
        spell: SPELLS.UNLEASH_LIFE_TALENT,
        buffSpellId: SPELLS.UNLEASH_LIFE_TALENT.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 15,
        timelineSortIndex: 5,
        isOnGCD: true,
        enabled: combatant.lv15Talent === SPELLS.UNLEASH_LIFE_TALENT.id,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.50,
          averageIssueEfficiency: 0.70,
          recommendedEfficiency: 0.90,
        },
      },
      {
        spell: SPELLS.ASCENDANCE_TALENT_RESTORATION,
        buffSpellId: SPELLS.ASCENDANCE_TALENT_RESTORATION.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
        enabled: combatant.lv100Talent === SPELLS.ASCENDANCE_TALENT_RESTORATION.id,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.2,
          averageIssueEfficiency: 0.5,
          recommendedEfficiency: 0.8,
        },
      },
      {
        spell: SPELLS.HEALING_TIDE_TOTEM_CAST,
        buffSpellId: SPELLS.HEALING_TIDE_TOTEM_CAST.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.2,
          averageIssueEfficiency: 0.5,
          recommendedEfficiency: 0.8,
        },
      },
      {
        spell: SPELLS.SPIRIT_LINK_TOTEM,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isOnGCD: true,
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.2,
          averageIssueEfficiency: 0.4,
          recommendedEfficiency: 0.6,
        },
      },
      {
        spell: SPELLS.HEALING_WAVE,
        timelineSortIndex: 13,
        isOnGCD: true,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        castEfficiency: {
          casts: castCount => (castCount.casts || 0) - (castCount.healingTwHits || 0),
        },
      },
      {
        spell: SPELLS.HEALING_WAVE,
        name: `Tidal Waved ${SPELLS.HEALING_WAVE.name}`,
        timelineSortIndex: 13,
        isOnGCD: true,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        castEfficiency: {
          casts: castCount => castCount.healingTwHits || 0,
        },
      },
      {
        spell: SPELLS.HEALING_SURGE_RESTORATION,
        timelineSortIndex: 14,
        isOnGCD: true,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        castEfficiency: {
          casts: castCount => (castCount.casts || 0) - (castCount.healingTwHits || 0),
        },
      },
      {
        spell: SPELLS.HEALING_SURGE_RESTORATION,
        name: `Tidal Waved ${SPELLS.HEALING_SURGE_RESTORATION.name}`,
        timelineSortIndex: 14,
        isOnGCD: true,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        castEfficiency: {
          casts: castCount => castCount.healingTwHits || 0,
        },
      },
      {
        spell: SPELLS.CHAIN_HEAL,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        timelineSortIndex: 12,
        isOnGCD: true,
      },
      {
        spell: SPELLS.PURIFY_SPIRIT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        cooldown: 8,
        isOnGCD: true,
      },
      {
        spell: SPELLS.FLAME_SHOCK_RESTORATION,
        buffSpellId: SPELLS.FLAME_SHOCK_RESTORATION.id,
        category: Abilities.SPELL_CATEGORIES.HEALER_DAMAGING_SPELL,
        timelineSortIndex: 60,
        cooldown: 6,
        isOnGCD: true,
      },
      {
        spell: SPELLS.LAVA_BURST,
        buffSpellId: SPELLS.LAVA_SURGE.id,
        category: Abilities.SPELL_CATEGORIES.HEALER_DAMAGING_SPELL,
        charges: combatant.hasTalent(SPELLS.ECHO_OF_THE_ELEMENTS_TALENT.id) ? 2 : 1,
        timelineSortIndex: 60,
        cooldown: 8,
        isOnGCD: true,
      },
      {
        spell: SPELLS.LIGHTNING_BOLT_RESTORATION,
        category: Abilities.SPELL_CATEGORIES.HEALER_DAMAGING_SPELL,
        isOnGCD: true,
        timelineSortIndex: 60,
      },
      {
        spell: SPELLS.CHAIN_LIGHTNING_RESTORATION,
        category: Abilities.SPELL_CATEGORIES.HEALER_DAMAGING_SPELL,
        isOnGCD: true,
        timelineSortIndex: 60,
      },
      {
        spell: SPELLS.GHOST_WOLF,
        buffSpellId: SPELLS.GHOST_WOLF.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
      },
      {
        spell: SPELLS.SPIRITWALKERS_GRACE,
        buffSpellId: SPELLS.SPIRITWALKERS_GRACE.id,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: combatant.hasTalent(SPELLS.GRACEFUL_SPIRIT_TALENT.id) ? 60 : 120,
        timelineSortIndex: 81,
      },
      {
        spell: SPELLS.EARTHBIND_TOTEM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
        cooldown: 30,
      },
      {
        spell: SPELLS.PURGE,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
      },
      {
        spell: SPELLS.LIGHTNING_SURGE_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
        cooldown: 60,
      },
      {
        spell: SPELLS.WIND_RUSH_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.WIND_RUSH_TOTEM_TALENT.id),
        cooldown: 120,
      },
      {
        spell: SPELLS.EARTHGRAB_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.EARTHGRAB_TOTEM_TALENT.id),
        cooldown: 30,
      },
      {
        spell: SPELLS.ANCESTRAL_PROTECTION_TOTEM_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.ANCESTRAL_PROTECTION_TOTEM_TALENT.id),
        cooldown: 300,
      },
      {
        spell: SPELLS.REINCARNATION,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 1800,
      },
      {
        spell: SPELLS.WIND_SHEAR,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        timelineSortIndex: 80,
        cooldown: 12,
      },
      {
        spell: [SPELLS.HEX, SPELLS.HEX_RAPTOR, SPELLS.HEX_SNAKE, SPELLS.HEX_SPIDER, SPELLS.HEX_COCKROACH, SPELLS.HEX_SKELETAL],
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        isOnGCD: true,
        timelineSortIndex: 80,
        cooldown: 30,
      },
      {
        spell: SPELLS.EARTH_SHIELD_TALENT,
        category: Abilities.SPELL_CATEGORIES.OTHERS,
        isOnGCD: true,
        enabled: combatant.hasTalent(SPELLS.EARTH_SHIELD_TALENT.id),
        timelineSortIndex: 80,
      },
      {
        spell: SPELLS.TREMOR_TOTEM,
        category: Abilities.SPELL_CATEGORIES.UTILITY,
        cooldown: 60,
        isOnGCD: true,
        timelineSortIndex: 80,
      },
      {
        spell: SPELLS.DOWNPOUR_TALENT,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 35, // CD changes depending on amount of effective targets hit (0 = 5s, 6 = 35s)
        isOnGCD: true,
        timelineSortIndex: 20,
        enabled: combatant.hasTalent(SPELLS.DOWNPOUR_TALENT.id),
        castEfficiency: {
          suggestion: true,
          majorIssueEfficiency: 0.20,
          averageIssueEfficiency: 0.40,
          recommendedEfficiency: 0.60,
        },
      },
      {
        spell: SPELLS.BERSERKING,
        buffSpellId: SPELLS.BERSERKING.id,
        category: Abilities.SPELL_CATEGORIES.COOLDOWNS,
        cooldown: 180,
        isUndetectable: true,
      },
    ];
  }
}

export default Abilities;
<|endoftext|>


<commit_before>/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var setCursor = require('./setCursor');

var STASHATTR = 'data-savedcursor';

/*
 * works with our CSS cursor classes (see css/_cursor.scss)
 * to override a previous cursor set on d3 single-element selections,
 * by moving the name of the original cursor to the data-savedcursor attr.
 * omit cursor to revert to the previously set value.
 */
module.exports = function overrideCursor(el3, csr) {
    var savedCursor = el3.attr(STASHATTR);
    if(csr) {
        if(savedCursor) {
            setCursor(el3, csr);
        }
        else {
            var classes = (el3.attr('class') || '').split(' ');
            for(var i = 0; i < classes.length; i++) {
                var cls = classes[i];
                if(cls.indexOf('cursor-') === 0) {
                    el3.attr(STASHATTR, cls.substr(7))
                        .classed(cls, false);
                }
            }
        }
    }
    else if(savedCursor) {
        el3.attr(STASHATTR, null);
        setCursor(el3, savedCursor);
    }
};
<commit_msg>fix require caitalization setCursor -> setcursor<commit_after>/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var setCursor = require('./setcursor');

var STASHATTR = 'data-savedcursor';

/*
 * works with our CSS cursor classes (see css/_cursor.scss)
 * to override a previous cursor set on d3 single-element selections,
 * by moving the name of the original cursor to the data-savedcursor attr.
 * omit cursor to revert to the previously set value.
 */
module.exports = function overrideCursor(el3, csr) {
    var savedCursor = el3.attr(STASHATTR);
    if(csr) {
        if(savedCursor) {
            setCursor(el3, csr);
        }
        else {
            var classes = (el3.attr('class') || '').split(' ');
            for(var i = 0; i < classes.length; i++) {
                var cls = classes[i];
                if(cls.indexOf('cursor-') === 0) {
                    el3.attr(STASHATTR, cls.substr(7))
                        .classed(cls, false);
                }
            }
        }
    }
    else if(savedCursor) {
        el3.attr(STASHATTR, null);
        setCursor(el3, savedCursor);
    }
};
<|endoftext|>


<commit_before>(function (angular) {

  'use strict';

  var fechaConFormatoModule = angular.module('fechaConFormatoModule', ['ngAnimate', 'ngSanitize', 'ui.bootstrap']);

  fechaConFormatoModule.controller('pimcFechaConFormatoController', ['pimcService', '$filter', '$window', function (pimcService, $filter, $window) {
    var pimcFechaConFormatoCtrl = this;

    // Lista de formatos
    pimcFechaConFormatoCtrl.listaFormatosPosibles = [
      {
        value: 1,
        template: "YYYY",
        formatoFecha: "yyyy",
        formatoHora: "",
        texto: "1990",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'year',
          formatYear: 'yyyy',
          minMode: 'year',
          yearRows: 5,
          yearColumns: 7
        }
      },
      {
        value: 2,
        template: "MMMM",
        formatoFecha: "MMMM",
        formatoHora: "",
        texto: "Abril",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'month',
          formatMonth: 'MMMM',
          minMode: 'month',
          maxMode: 'month'
        }
      },
      {
        value: 3,
        template: "MMMM, YYYY",
        formatoFecha: "MMMM, yyyy",
        formatoHora: "",
        texto: "Abril, 1990",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'month',
          formatYear: 'MMMM',
          formatYear: 'yyyy',
          minMode: 'month',
          maxMode: 'year'
        }
      },
      {
        value: 4,
        template: "DD-MMMM",
        formatoFecha: "dd/MMMM",
        formatoHora: "",
        texto: "02/Abril",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'month',
          formatDay: 'dd',
          formatYear: 'yyyy',
          minMode: 'day',
          maxMode: 'month'
        }
      },
      {
        value: 5,
        template: "D-M-YYYY",
        formatoFecha: "d/M/yyyy",
        formatoHora: "",
        texto: "2/4/1990",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'day',
          formatDay: 'd',
          formatMonth: 'M',
          formatYear: 'yyyy',
          minMode: 'day',
          maxMode: 'year'
        }
      },
      {
        value: 6,
        template: "DD-MM-YYYY",
        formatoFecha: "dd/MM/yyyy",
        formatoHora: "",
        texto: "02/04/1990",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'day',
          formatDay: 'dd',
          formatMonth: 'MM',
          formatYear: 'yyyy',
          minMode: 'day',
          maxMode: 'year'
        }
      },
      {
        value: 7,
        template: "h:mm A",
        formatoFecha: "",
        formatoHora: "h:mm a",
        texto: "4:00 PM",
        fechaActivado: false,
        horaActivado: true,
        datepickerOptions: {}
      },
      {
        value: 8,
        template: "DD-MM-YYYY HH:mm",
        formatoFecha: "dd-MM-yyyy",
        formatoHora: "hh:mm",
        texto: "4:00 - 02/07/1990",
        fechaActivado: true,
        horaActivado: true,
        datepickerOptions: {
          datepickerMode: 'day',
          formatDay: 'dd',
          formatMonth: 'MM',
          formatYear: 'yyyy',
          minMode: 'day',
          maxMode: 'year'
        }
      }
    ];

    // Valores internos
    pimcFechaConFormatoCtrl.fechaInt = "";
    pimcFechaConFormatoCtrl.fechaFormatoInt = "";
    pimcFechaConFormatoCtrl.formatoSeleccionado = pimcFechaConFormatoCtrl.listaFormatosPosibles[7];

    pimcFechaConFormatoCtrl.$onInit = function () {
      pimcFechaConFormatoCtrl.formatoSeleccionado = pimcFechaConFormatoCtrl.listaFormatosPosibles[7];
      pimcFechaConFormatoCtrl.listaFormatosPosibles.forEach(function (e) {
        if (e.template === pimcFechaConFormatoCtrl.formato) {
          pimcFechaConFormatoCtrl.formatoSeleccionado = e;
        }
      });
    };

    pimcFechaConFormatoCtrl.$onChanges = function (changes) {
      if (changes.fecha) {
        pimcFechaConFormatoCtrl.fechaInt = $window.angular.copy(pimcFechaConFormatoCtrl.fecha);
      }
      if (changes.formato) {
        pimcFechaConFormatoCtrl.fechaFormatoInt = $window.angular.copy(pimcFechaConFormatoCtrl.formato);
        pimcFechaConFormatoCtrl.formatoSeleccionado = pimcFechaConFormatoCtrl.listaFormatosPosibles[7];
        pimcFechaConFormatoCtrl.listaFormatosPosibles.forEach(function (e) {
          if (e.template === pimcFechaConFormatoCtrl.fechaFormatoInt) {
            pimcFechaConFormatoCtrl.formatoSeleccionado = e;
          }
        });
      }
    };

    // Cuando se cambia el formato se le reporta a la unidad externa
    pimcFechaConFormatoCtrl.cambiarFormato = function () {
      pimcFechaConFormatoCtrl.reportarCambio({
        fecha: pimcFechaConFormatoCtrl.fechaInt,
        formato: pimcFechaConFormatoCtrl.fechaFormatoInt.template
      });
    };
    pimcFechaConFormatoCtrl.fechaCambio = function () {
      pimcFechaConFormatoCtrl.reportarCambio({
        fecha: pimcFechaConFormatoCtrl.fechaInt,
        formato: pimcFechaConFormatoCtrl.fechaFormatoInt.template
      });
    }

    pimcFechaConFormatoCtrl.opened = {};
    pimcFechaConFormatoCtrl.open = function ($event, elementOpened) {
      $event.preventDefault();
      $event.stopPropagation();

      pimcFechaConFormatoCtrl.opened[elementOpened] = !pimcFechaConFormatoCtrl.opened[elementOpened];
    };

  }]);

  // COMPONENTS
  fechaConFormatoModule.component('pimcFechaConFormato', {
    bindings: {
      fecha: "<",
      formato: "<",
      reportarCambio: "&"
    },
    controller: 'pimcFechaConFormatoController',
    controllerAs: 'pimcFechaConFormatoCtrl',
    templateUrl: 'views/global/fechaConFormato/fechaConFormatoTemplate.html'
  });

})(window.angular);<commit_msg>Cambios importantes a fecha con formato<commit_after>(function (angular) {

  'use strict';

  var fechaConFormatoModule = angular.module('fechaConFormatoModule', ['ngAnimate', 'ngSanitize', 'xeditable', 'ui.bootstrap']);

  fechaConFormatoModule.controller('pimcFechaConFormatoController', ['pimcService', '$filter', '$window', function (pimcService, $filter, $window) {
    var pimcFechaConFormatoCtrl = this;

    // Lista de formatos
    pimcFechaConFormatoCtrl.listaFormatosPosibles = [
      {
        value: 1,
        template: "YYYY",
        formatoFecha: "yyyy",
        formatoHora: "",
        texto: "1990",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'year',
          formatYear: 'yyyy',
          minMode: 'year',
          yearRows: 5,
          yearColumns: 7
        }
      },
      {
        value: 2,
        template: "MMMM",
        formatoFecha: "MMMM",
        formatoHora: "",
        texto: "Abril",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'month',
          formatMonth: 'MMMM',
          minMode: 'month',
          maxMode: 'month'
        }
      },
      {
        value: 3,
        template: "MMMM, YYYY",
        formatoFecha: "MMMM, yyyy",
        formatoHora: "",
        texto: "Abril, 1990",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'month',
          formatYear: 'MMMM',
          formatYear: 'yyyy',
          minMode: 'month',
          maxMode: 'year'
        }
      },
      {
        value: 4,
        template: "DD-MMMM",
        formatoFecha: "dd/MMMM",
        formatoHora: "",
        texto: "02/Abril",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'month',
          formatDay: 'dd',
          formatYear: 'yyyy',
          minMode: 'day',
          maxMode: 'month'
        }
      },
      {
        value: 5,
        template: "D-M-YYYY",
        formatoFecha: "d/M/yyyy",
        formatoHora: "",
        texto: "2/4/1990",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'day',
          formatDay: 'd',
          formatMonth: 'M',
          formatYear: 'yyyy',
          minMode: 'day',
          maxMode: 'year'
        }
      },
      {
        value: 6,
        template: "DD-MM-YYYY",
        formatoFecha: "dd/MM/yyyy",
        formatoHora: "",
        texto: "02/04/1990",
        fechaActivado: true,
        horaActivado: false,
        datepickerOptions: {
          datepickerMode: 'day',
          formatDay: 'dd',
          formatMonth: 'MM',
          formatYear: 'yyyy',
          minMode: 'day',
          maxMode: 'year'
        }
      },
      {
        value: 7,
        template: "h:mm A",
        formatoFecha: "",
        formatoHora: "h:mm a",
        texto: "4:00 PM",
        fechaActivado: false,
        horaActivado: true,
        datepickerOptions: {}
      },
      {
        value: 8,
        template: "DD-MM-YYYY HH:mm",
        formatoFecha: "dd-MM-yyyy",
        formatoHora: "hh:mm",
        texto: "4:00 - 02/07/1990",
        fechaActivado: true,
        horaActivado: true,
        datepickerOptions: {
          datepickerMode: 'day',
          formatDay: 'dd',
          formatMonth: 'MM',
          formatYear: 'yyyy',
          minMode: 'day',
          maxMode: 'year'
        }
      }
    ];

    // Valores internos
    pimcFechaConFormatoCtrl.fechaInt = "";
    pimcFechaConFormatoCtrl.fechaFormatoInt = "";
    pimcFechaConFormatoCtrl.formatoSeleccionado = pimcFechaConFormatoCtrl.listaFormatosPosibles[7];

    pimcFechaConFormatoCtrl.$onInit = function () {
      pimcFechaConFormatoCtrl.formatoSeleccionado = pimcFechaConFormatoCtrl.listaFormatosPosibles[7];
      pimcFechaConFormatoCtrl.listaFormatosPosibles.forEach(function (e) {
        if (e.template === pimcFechaConFormatoCtrl.formato) {
          pimcFechaConFormatoCtrl.formatoSeleccionado = e;
        }
      });
    };

    pimcFechaConFormatoCtrl.$onChanges = function (changes) {
      if (changes.fecha) {
        pimcFechaConFormatoCtrl.fechaInt = $window.angular.copy(pimcFechaConFormatoCtrl.fecha);
        if (!pimcFechaConFormatoCtrl.fechaInt || pimcFechaConFormatoCtrl.fechaInt.lenght === 0 ) {
          pimcFechaConFormatoCtrl.fechaInt = "0";
        }
      }
      if (changes.formato) {
        pimcFechaConFormatoCtrl.fechaFormatoInt = $window.angular.copy(pimcFechaConFormatoCtrl.formato);
        pimcFechaConFormatoCtrl.formatoSeleccionado = pimcFechaConFormatoCtrl.listaFormatosPosibles[7];
        pimcFechaConFormatoCtrl.listaFormatosPosibles.forEach(function (e) {
          if (e.template === pimcFechaConFormatoCtrl.fechaFormatoInt) {
            pimcFechaConFormatoCtrl.formatoSeleccionado = e;
          }
        });
      }
    };

    // Cuando se cambia el formato se le reporta a la unidad externa
    pimcFechaConFormatoCtrl.cambiarFormato = function (nuevoFormato) {
      pimcFechaConFormatoCtrl.reportarCambio({
        fecha: pimcFechaConFormatoCtrl.fechaInt,
        formato: pimcFechaConFormatoCtrl.formatoSeleccionado.template
      });
    };
    pimcFechaConFormatoCtrl.fechaCambio = function () {
      pimcFechaConFormatoCtrl.reportarCambio({
        fecha: pimcFechaConFormatoCtrl.fechaInt,
        formato: pimcFechaConFormatoCtrl.formatoSeleccionado.template
      });
    }

    pimcFechaConFormatoCtrl.opened = {};
    pimcFechaConFormatoCtrl.open = function ($event, elementOpened) {
      $event.preventDefault();
      $event.stopPropagation();

      pimcFechaConFormatoCtrl.opened[elementOpened] = !pimcFechaConFormatoCtrl.opened[elementOpened];
    };

  }]);

  // COMPONENTS
  fechaConFormatoModule.component('pimcFechaConFormato', {
    bindings: {
      fecha: "<",
      formato: "<",
      reportarCambio: "&"
    },
    controller: 'pimcFechaConFormatoController',
    controllerAs: 'pimcFechaConFormatoCtrl',
    templateUrl: 'views/global/fechaConFormato/fechaConFormatoTemplate.html'
  });

  fechaConFormatoModule.run(function(editableOptions, editableThemes) {
    editableThemes.bs3.inputClass = 'input-sm';
    editableThemes.bs3.buttonsClass = 'btn-sm';
    editableOptions.theme = 'bs3';
    editableOptions.buttons = 'no';
});

})(window.angular);<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const office2pdf = require('office2pdf');
const client = require('../../db/client.js');
const mailgun = require('../utils/mailgun.js');
const layout = { layout: 'client' };

const emailAgencyOnClientShortlistAcceptance = require('../utils/mailgun.js').emailAgencyOnClientShortlistAcceptance;
const emailAgencyOnClientShortlistRejection = require('../utils/mailgun.js').emailAgencyOnClientShortlistRejection;
const addRemoveFromSets = require('../../db/redis.js').addRemoveFromSets;
const getVacancyDetails = require('../../db/redis.js').getVacancyDetails;
const getSetMembersInfo = require('../../db/redis.js').getSetMembersInfo;
const removeVacancy = require('../../db/redis.js').removeVacancy;
const formatReason = require('../utils/app.js').formatReason;
const removeCV = require('../../db/redis.js').removeCV;
const getHash = require('../../db/redis.js').getHash;

exports.register = (server, options, next) => {

    server.route([{
        method: 'GET',
        path: '/client/job/{vid}',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let vid = request.params.vid;
                let opts = { title: 'Job page', vid: vid };

                getVacancyDetails(vid, (res) => {
                    if (res) {
                        opts.data = res;
                        let shortlist = vid + 'clientShortlist';
                        getSetMembersInfo(shortlist, (res) => {
                            if (res) {
                                opts.cvs = res;
                            } else {
                                opts.message = 'There are no candidates for you to approve at the moment... come back when there are!';
                            }
                            reply.view('clientjob', opts, layout);
                        });
                    } else {
                        opts.message = 'Sorry, something went wrong, please try again';
                        reply.view('message', opts, layout);
                    }
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/job/remove/{vid}',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let opts = { title: 'Removal successful!' };
                let vid = request.params.vid;
                let id = request.state.user.profile.id;

                removeVacancy(vid, id, (res) => {
                    if (res) {
                        opts.message = 'You have successfully removed this vacancy... Return home';
                    } else {
                        opts.title = 'Sorry...';
                        opts.message = 'Something went wrong, please go back and try again';
                    }
                    reply.view('message', opts, layout);
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/job/reject',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let agencyEmail = request.query.email;
                let reason = request.query.reason;
                let cvid = request.query.cvid;
                let vid = request.query.vid;
                let set = vid + 'clientShortlist';

                getHash(vid, (res) => {
                    if (!res) {
                        return reply(false);
                    }

                    let jobTitle = res.jobTitle;
                    let companyName = res.companyName;

                    getHash(cvid, (res) => {
                        if (!res) {
                            return reply(false);
                        }

                        let candidateName = res.candidateName;
                        let formattedReason = formatReason(reason);

                        emailAgencyOnClientShortlistRejection.to = agencyEmail;
                        emailAgencyOnClientShortlistRejection.html = emailAgencyOnClientShortlistRejection.html.replace('-candidateName-', candidateName).replace('-jobTitle-', jobTitle).replace('-companyName-', companyName).replace('-formattedReason-', formattedReason);
                        mailgun.messages().send(emailAgencyOnClientShortlistRejection);

                        removeCV(cvid, set, (res) => { // has to be here otherwise cvid won't exist for the above email function
                            if (res) {
                                reply(true);
                            } else {
                                reply(false);
                            }
                        });
                    });
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/job/accept',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let vid = request.query.vid;
                let cvid = request.query.cvid;
                let agencyId = request.query.agencyId;
                let jobTitle = request.query.jobTitle;
                let clientId = request.auth.credentials.profile.id;
                let agencyEmail = request.query.email;
                let emailContext = emailAgencyOnClientShortlistAcceptance;
                let candidateName = request.query.candidateName;

                emailContext.to = agencyEmail;
                emailContext.subject = 'Great news! ' + candidateName + ' has been accepted';

                client.sadd(agencyId + 'agencyScheduling', vid, (err) => {
                    if (err) {
                        reply(false);
                    } else {
                        addRemoveFromSets(vid + 'stageOne', vid + 'clientShortlist', cvid, (res) => {
                            if (res) {

                                client.hget(clientId, 'companyName', (err, res) => {
                                    let clientName = res;
                                    client.hget(agencyId, 'contactName', (err, res) => {
                                        let firstName = res.split(' ')[0];
                                        emailContext.html = emailContext.html.replace('-candidateName-', candidateName).replace('-jobTitle-', jobTitle).replace('-firstName-', firstName).replace('-clientName-', clientName);
                                        mailgun.messages().send(emailContext);
                                    });
                                });

                                client.hset(cvid, 'stage', 'stageOne', (err, res) => {
                                    if (res) {
                                        reply(true);
                                    } else {
                                        reply(false);
                                    }
                                });
                            } else {
                                reply(false);
                            }
                        });
                    }
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/file-exists',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let cvUrl = request.query.cvUrl;
                let fileName = cvUrl.split(process.env.BUCKET_URL)[1];
                let filePath = '/public/assets/Downloads/';
                let existingFilesInDownload = fs.readdirSync(filePath);
                existingFilesInDownload.indexOf(fileName) > -1 ?
                    reply(fileName) : reply('notFound');
            }
        }
    }, {
        method: 'GET',
        path: '/client/convert-file',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let fileName = request.query.fileName;
                console.log(fileName);
                let filePath = '/public/assets/Downloads/';

                office2pdf.generatePdf(filePath + fileName, function(error, result) {
                    console.log('officetoPdf', result);
                    error ? reply('error') : reply('success');
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/clear-downloads',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let filePath = '/public/assets/Downloads/';
                let existingFilesInDownload = fs.readdirSync(filePath);
                existingFilesInDownload.forEach((file, index) => {
                    fs.unlink(filePath + file, (error) => {
                        if (error) {
                            console.log(error);
                        } else {
                            console.log('deleted');
                            if (index === existingFilesInDownload.length) {
                                reply(true);
                            }
                        }

                    });
                });
            }
        }
    }]);
    return next();
};

exports.register.attributes = {
    name: 'ClientJob'
};
<commit_msg>try different path to Downloads<commit_after>'use strict';

const fs = require('fs');
const office2pdf = require('office2pdf');
const client = require('../../db/client.js');
const mailgun = require('../utils/mailgun.js');
const layout = { layout: 'client' };

const emailAgencyOnClientShortlistAcceptance = require('../utils/mailgun.js').emailAgencyOnClientShortlistAcceptance;
const emailAgencyOnClientShortlistRejection = require('../utils/mailgun.js').emailAgencyOnClientShortlistRejection;
const addRemoveFromSets = require('../../db/redis.js').addRemoveFromSets;
const getVacancyDetails = require('../../db/redis.js').getVacancyDetails;
const getSetMembersInfo = require('../../db/redis.js').getSetMembersInfo;
const removeVacancy = require('../../db/redis.js').removeVacancy;
const formatReason = require('../utils/app.js').formatReason;
const removeCV = require('../../db/redis.js').removeCV;
const getHash = require('../../db/redis.js').getHash;

exports.register = (server, options, next) => {

    server.route([{
        method: 'GET',
        path: '/client/job/{vid}',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let vid = request.params.vid;
                let opts = { title: 'Job page', vid: vid };

                getVacancyDetails(vid, (res) => {
                    if (res) {
                        opts.data = res;
                        let shortlist = vid + 'clientShortlist';
                        getSetMembersInfo(shortlist, (res) => {
                            if (res) {
                                opts.cvs = res;
                            } else {
                                opts.message = 'There are no candidates for you to approve at the moment... come back when there are!';
                            }
                            reply.view('clientjob', opts, layout);
                        });
                    } else {
                        opts.message = 'Sorry, something went wrong, please try again';
                        reply.view('message', opts, layout);
                    }
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/job/remove/{vid}',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let opts = { title: 'Removal successful!' };
                let vid = request.params.vid;
                let id = request.state.user.profile.id;

                removeVacancy(vid, id, (res) => {
                    if (res) {
                        opts.message = 'You have successfully removed this vacancy... Return home';
                    } else {
                        opts.title = 'Sorry...';
                        opts.message = 'Something went wrong, please go back and try again';
                    }
                    reply.view('message', opts, layout);
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/job/reject',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let agencyEmail = request.query.email;
                let reason = request.query.reason;
                let cvid = request.query.cvid;
                let vid = request.query.vid;
                let set = vid + 'clientShortlist';

                getHash(vid, (res) => {
                    if (!res) {
                        return reply(false);
                    }

                    let jobTitle = res.jobTitle;
                    let companyName = res.companyName;

                    getHash(cvid, (res) => {
                        if (!res) {
                            return reply(false);
                        }

                        let candidateName = res.candidateName;
                        let formattedReason = formatReason(reason);

                        emailAgencyOnClientShortlistRejection.to = agencyEmail;
                        emailAgencyOnClientShortlistRejection.html = emailAgencyOnClientShortlistRejection.html.replace('-candidateName-', candidateName).replace('-jobTitle-', jobTitle).replace('-companyName-', companyName).replace('-formattedReason-', formattedReason);
                        mailgun.messages().send(emailAgencyOnClientShortlistRejection);

                        removeCV(cvid, set, (res) => { // has to be here otherwise cvid won't exist for the above email function
                            if (res) {
                                reply(true);
                            } else {
                                reply(false);
                            }
                        });
                    });
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/job/accept',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let vid = request.query.vid;
                let cvid = request.query.cvid;
                let agencyId = request.query.agencyId;
                let jobTitle = request.query.jobTitle;
                let clientId = request.auth.credentials.profile.id;
                let agencyEmail = request.query.email;
                let emailContext = emailAgencyOnClientShortlistAcceptance;
                let candidateName = request.query.candidateName;

                emailContext.to = agencyEmail;
                emailContext.subject = 'Great news! ' + candidateName + ' has been accepted';

                client.sadd(agencyId + 'agencyScheduling', vid, (err) => {
                    if (err) {
                        reply(false);
                    } else {
                        addRemoveFromSets(vid + 'stageOne', vid + 'clientShortlist', cvid, (res) => {
                            if (res) {

                                client.hget(clientId, 'companyName', (err, res) => {
                                    let clientName = res;
                                    client.hget(agencyId, 'contactName', (err, res) => {
                                        let firstName = res.split(' ')[0];
                                        emailContext.html = emailContext.html.replace('-candidateName-', candidateName).replace('-jobTitle-', jobTitle).replace('-firstName-', firstName).replace('-clientName-', clientName);
                                        mailgun.messages().send(emailContext);
                                    });
                                });

                                client.hset(cvid, 'stage', 'stageOne', (err, res) => {
                                    if (res) {
                                        reply(true);
                                    } else {
                                        reply(false);
                                    }
                                });
                            } else {
                                reply(false);
                            }
                        });
                    }
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/file-exists',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let cvUrl = request.query.cvUrl;
                let fileName = cvUrl.split(process.env.BUCKET_URL)[1];
                let filePath = 'public/assets/Downloads/';
                let existingFilesInDownload = fs.readdirSync(filePath);
                existingFilesInDownload.indexOf(fileName) > -1 ?
                    reply(fileName) : reply('notFound');
            }
        }
    }, {
        method: 'GET',
        path: '/client/convert-file',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let fileName = request.query.fileName;
                console.log(fileName);
                let filePath = 'public/assets/Downloads/';

                office2pdf.generatePdf(filePath + fileName, function(error, result) {
                    console.log('officetoPdf', result);
                    error ? reply('error') : reply('success');
                });
            }
        }
    }, {
        method: 'GET',
        path: '/client/clear-downloads',
        config: {
            auth: {
                strategy: 'hrns-cookie',
                scope: 'client'
            },
            handler: (request, reply) => {
                let filePath = 'public/assets/Downloads/';
                let existingFilesInDownload = fs.readdirSync(filePath);
                existingFilesInDownload.forEach((file, index) => {
                    fs.unlink(filePath + file, (error) => {
                        if (error) {
                            console.log(error);
                        } else {
                            console.log('deleted');
                            if (index === existingFilesInDownload.length) {
                                reply(true);
                            }
                        }

                    });
                });
            }
        }
    }]);
    return next();
};

exports.register.attributes = {
    name: 'ClientJob'
};
<|endoftext|>


<commit_before>var _ = require('lodash')
var simple = require('simple-mock')
var test = require('tape')

var init = require('../../lib/init')
var getApi = require('../../lib/get-api')
var getState = require('../../lib/get-state')

function findEventHandler (calls, name) {
  var call = _.find(calls, function (call) {
    return call.args[0] === name
  })

  return _.last(call.args)
}

test('"reset" triggered on "signin"', function (t) {
  t.plan(8)

  var signInTestOrder = []
  var existingObjects = [{id: 'foo', createdBy: 'accountid1'}]
  var hoodie = {
    account: {
      id: 'accountid1',
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      findAll: function () {
        return Promise.resolve(existingObjects)
      },
      add: function (objects) {
        t.deepEqual(objects, existingObjects, 'adding existing objects after sign in')
        return Promise.resolve(existingObjects)
      },
      connect: function () {
        t.pass('store.connect is called on "post:signin"')
        signInTestOrder.push('connect')
      },
      reset: function (options) {
        t.isNot(typeof options, 'undefined', 'store.reset options are defined')
        t.isNot(typeof options.name, 'undefined', 'store.reset options has defined name')
        t.pass('store.reset called on "post:signin"')
        signInTestOrder.push('reset')

        return Promise.resolve()
      }
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)

  var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin')
  var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin')

  var preHooks = []
  preSignInHandler({hooks: preHooks})

  var postHooks = []
  postSignInHandler({hooks: postHooks})

  t.is(preHooks.length, 1, 'one pre:signin hook registered')
  t.is(postHooks.length, 1, 'one post:signin hook registered')
  preHooks[0]().then(function () {
    // simulate new account.id
    hoodie.account.id = 'accountid2'
  })

  .then(postHooks[0])

  .then(function () {
    t.deepEqual(signInTestOrder, ['reset', 'connect'], 'store.connect was called after store.reset')
  })

  .catch(t.error)
})

test('is "reset" triggered on "post:signout"', function (t) {
  t.plan(4)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      reset: function (options) {
        t.isNot(typeof options, 'undefined', 'store.reset options are defined')
        t.isNot(typeof options.name, 'undefined', 'store.reset options has defined name')
        t.pass('store.reset called on "signout"')
      }
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  var signOutHandler = findEventHandler(hoodie.account.on.calls, 'post:signout')
  var hooks = []
  signOutHandler({hooks: hooks})
  t.is(hooks.length, 1, 'one post:signout hook registered')
  hooks[0]()
})

test('"hoodie.store.connect()" is called when "hoodie.account.isSignedIn()" returns "true" ', function (t) {
  t.plan(1)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub().returnWith(true)
    },
    store: {
      connect: simple.stub(),
      reset: simple.stub()
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  t.is(hoodie.store.connect.callCount, 1, 'calls hoodie account.connect once')
})

test('"hoodie.store.connect()" is *not* called when "hoodie.account.isSignedIn()" returns "false"', function (t) {
  t.plan(1)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub().returnWith(false)
    },
    store: {
      connect: simple.stub(),
      reset: simple.stub()
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  t.is(hoodie.store.connect.callCount, 0, 'does not hoodie account.connect')
})

test('hoodie.store gets initialized with options.ajax', function (t) {
  t.plan(1)

  var CustomStoreMock = simple.stub()
  simple.mock(getApi.internals, 'Account', function () {
    return {
      get: function (path) {
        return {
          id: path + '123'
        }
      }
    }
  })
  simple.mock(getApi.internals, 'Store', {
    defaults: function () { return CustomStoreMock }
  })

  var state = getState()
  getApi(state)

  var storeAjaxParam = CustomStoreMock.lastCall.args[1]
  t.is(storeAjaxParam.ajax().headers.authorization, 'Bearer session123',
    'sets ajax authorization header')
})

test('hoodie.store initialization without session', function (t) {
  t.plan(1)

  var CustomStoreMock = simple.stub()
  simple.mock(getApi.internals, 'Account', function () {
    return {
      get: function (path) {
        return undefined
      }
    }
  })
  simple.mock(getApi.internals, 'Store', {
    defaults: function () { return CustomStoreMock }
  })

  var state = getState()
  getApi(state)

  var storeAjaxParam = CustomStoreMock.lastCall.args[1]
  t.is(storeAjaxParam.ajax(), undefined, 'no authorization header without session')
})

test('"hoodie.store.push" is called on "pre:signout"', function (t) {
  t.plan(2)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      push: function () {
        t.pass('store.push called on "signout"')
      }
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  var signOutHandler = findEventHandler(hoodie.account.on.calls, 'pre:signout')
  var hooks = []
  signOutHandler({hooks: hooks})
  t.is(hooks.length, 1, 'one pre:signout hook registered')
  hooks[0]()
})

test('"hoodie.store.*" is *not* called when "hoodie.account.isSignedIn()" returns "false"', function (t) {
  t.plan(2)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub().returnWith(false)
    },
    store: {
      disconnect: simple.stub(),
      connect: simple.stub()
    },
    connectionStatus: {
      on: function (name, listener) {
        listener()
      }
    }
  }

  init(hoodie)
  t.is(hoodie.store.disconnect.callCount, 0, 'calls hoodie store.disconnect zero')
  t.is(hoodie.store.connect.callCount, 0, 'calls hoodie store.connect zero')
})

test('"hoodie.store.*" is called on "disconnect" and "connect"', function (t) {
  t.plan(2)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub().returnWith(true)
    },
    store: {
      disconnect: function (options) {
        t.pass('store.disconnect called on "disconnect"')
      },
      connect: function (options) {
        t.pass('store.connect called on "connect"')
      }
    },
    connectionStatus: {
      on: function (name, listener) {
        listener()
      },
      ok: false // else connect is called a second time
    }
  }

  init(hoodie)
})

test('"dataFromAccountBeforeSignin" nulled on "signin"', function (t) {
  t.plan(3)

  var existingObjects = [{id: 'foo', createdBy: 'accountid1'}]
  var hoodie = {
    account: {
      id: 'accountid1',
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      findAll: function () {
        return Promise.resolve(existingObjects)
      },
      connect: function () {
        t.pass('store.connect called on "signin"')
      }
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin')
  var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin')

  var preHooks = []
  preSignInHandler({hooks: preHooks})

  var postHooks = []
  postSignInHandler({hooks: postHooks})

  t.is(preHooks.length, 1, 'one pre:signin hook registered')
  t.is(postHooks.length, 1, 'one post:signin hook registered')
  preHooks[0]()
  .then(postHooks[0])
})

test('"signin" with Error', function (t) {
  t.plan(2)

  var existingObjects = [{id: 'foo', createdBy: 'accountid1'}]
  var hoodie = {
    account: {
      id: 'accountid1',
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      findAll: function () {
        return Promise.resolve(existingObjects)
      },
      reset: simple.stub().rejectWith(new Error('Ooops'))
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin')
  var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin')

  var preHooks = []
  preSignInHandler({hooks: preHooks})

  var postHooks = []
  postSignInHandler({hooks: postHooks})

  t.is(preHooks.length, 1, 'one pre:signin hook registered')
  t.is(postHooks.length, 1, 'one post:signin hook registered')
  preHooks[0]().then(function () {
    // simulate new account.id
    hoodie.account.id = 'accountid2'
  })
  .then(postHooks[0])
})
<commit_msg>test: rename bearer token to session<commit_after>var _ = require('lodash')
var simple = require('simple-mock')
var test = require('tape')

var init = require('../../lib/init')
var getApi = require('../../lib/get-api')
var getState = require('../../lib/get-state')

function findEventHandler (calls, name) {
  var call = _.find(calls, function (call) {
    return call.args[0] === name
  })

  return _.last(call.args)
}

test('"reset" triggered on "signin"', function (t) {
  t.plan(8)

  var signInTestOrder = []
  var existingObjects = [{id: 'foo', createdBy: 'accountid1'}]
  var hoodie = {
    account: {
      id: 'accountid1',
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      findAll: function () {
        return Promise.resolve(existingObjects)
      },
      add: function (objects) {
        t.deepEqual(objects, existingObjects, 'adding existing objects after sign in')
        return Promise.resolve(existingObjects)
      },
      connect: function () {
        t.pass('store.connect is called on "post:signin"')
        signInTestOrder.push('connect')
      },
      reset: function (options) {
        t.isNot(typeof options, 'undefined', 'store.reset options are defined')
        t.isNot(typeof options.name, 'undefined', 'store.reset options has defined name')
        t.pass('store.reset called on "post:signin"')
        signInTestOrder.push('reset')

        return Promise.resolve()
      }
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)

  var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin')
  var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin')

  var preHooks = []
  preSignInHandler({hooks: preHooks})

  var postHooks = []
  postSignInHandler({hooks: postHooks})

  t.is(preHooks.length, 1, 'one pre:signin hook registered')
  t.is(postHooks.length, 1, 'one post:signin hook registered')
  preHooks[0]().then(function () {
    // simulate new account.id
    hoodie.account.id = 'accountid2'
  })

  .then(postHooks[0])

  .then(function () {
    t.deepEqual(signInTestOrder, ['reset', 'connect'], 'store.connect was called after store.reset')
  })

  .catch(t.error)
})

test('is "reset" triggered on "post:signout"', function (t) {
  t.plan(4)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      reset: function (options) {
        t.isNot(typeof options, 'undefined', 'store.reset options are defined')
        t.isNot(typeof options.name, 'undefined', 'store.reset options has defined name')
        t.pass('store.reset called on "signout"')
      }
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  var signOutHandler = findEventHandler(hoodie.account.on.calls, 'post:signout')
  var hooks = []
  signOutHandler({hooks: hooks})
  t.is(hooks.length, 1, 'one post:signout hook registered')
  hooks[0]()
})

test('"hoodie.store.connect()" is called when "hoodie.account.isSignedIn()" returns "true" ', function (t) {
  t.plan(1)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub().returnWith(true)
    },
    store: {
      connect: simple.stub(),
      reset: simple.stub()
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  t.is(hoodie.store.connect.callCount, 1, 'calls hoodie account.connect once')
})

test('"hoodie.store.connect()" is *not* called when "hoodie.account.isSignedIn()" returns "false"', function (t) {
  t.plan(1)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub().returnWith(false)
    },
    store: {
      connect: simple.stub(),
      reset: simple.stub()
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  t.is(hoodie.store.connect.callCount, 0, 'does not hoodie account.connect')
})

test('hoodie.store gets initialized with options.ajax', function (t) {
  t.plan(1)

  var CustomStoreMock = simple.stub()
  simple.mock(getApi.internals, 'Account', function () {
    return {
      get: function (path) {
        return {
          id: path + '123'
        }
      }
    }
  })
  simple.mock(getApi.internals, 'Store', {
    defaults: function () { return CustomStoreMock }
  })

  var state = getState()
  getApi(state)

  var storeAjaxParam = CustomStoreMock.lastCall.args[1]
  t.is(storeAjaxParam.ajax().headers.authorization, 'Session session123',
    'sets ajax authorization header')
})

test('hoodie.store initialization without session', function (t) {
  t.plan(1)

  var CustomStoreMock = simple.stub()
  simple.mock(getApi.internals, 'Account', function () {
    return {
      get: function (path) {
        return undefined
      }
    }
  })
  simple.mock(getApi.internals, 'Store', {
    defaults: function () { return CustomStoreMock }
  })

  var state = getState()
  getApi(state)

  var storeAjaxParam = CustomStoreMock.lastCall.args[1]
  t.is(storeAjaxParam.ajax(), undefined, 'no authorization header without session')
})

test('"hoodie.store.push" is called on "pre:signout"', function (t) {
  t.plan(2)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      push: function () {
        t.pass('store.push called on "signout"')
      }
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  var signOutHandler = findEventHandler(hoodie.account.on.calls, 'pre:signout')
  var hooks = []
  signOutHandler({hooks: hooks})
  t.is(hooks.length, 1, 'one pre:signout hook registered')
  hooks[0]()
})

test('"hoodie.store.*" is *not* called when "hoodie.account.isSignedIn()" returns "false"', function (t) {
  t.plan(2)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub().returnWith(false)
    },
    store: {
      disconnect: simple.stub(),
      connect: simple.stub()
    },
    connectionStatus: {
      on: function (name, listener) {
        listener()
      }
    }
  }

  init(hoodie)
  t.is(hoodie.store.disconnect.callCount, 0, 'calls hoodie store.disconnect zero')
  t.is(hoodie.store.connect.callCount, 0, 'calls hoodie store.connect zero')
})

test('"hoodie.store.*" is called on "disconnect" and "connect"', function (t) {
  t.plan(2)

  var hoodie = {
    account: {
      id: 0,
      on: simple.stub(),
      isSignedIn: simple.stub().returnWith(true)
    },
    store: {
      disconnect: function (options) {
        t.pass('store.disconnect called on "disconnect"')
      },
      connect: function (options) {
        t.pass('store.connect called on "connect"')
      }
    },
    connectionStatus: {
      on: function (name, listener) {
        listener()
      },
      ok: false // else connect is called a second time
    }
  }

  init(hoodie)
})

test('"dataFromAccountBeforeSignin" nulled on "signin"', function (t) {
  t.plan(3)

  var existingObjects = [{id: 'foo', createdBy: 'accountid1'}]
  var hoodie = {
    account: {
      id: 'accountid1',
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      findAll: function () {
        return Promise.resolve(existingObjects)
      },
      connect: function () {
        t.pass('store.connect called on "signin"')
      }
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin')
  var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin')

  var preHooks = []
  preSignInHandler({hooks: preHooks})

  var postHooks = []
  postSignInHandler({hooks: postHooks})

  t.is(preHooks.length, 1, 'one pre:signin hook registered')
  t.is(postHooks.length, 1, 'one post:signin hook registered')
  preHooks[0]()
  .then(postHooks[0])
})

test('"signin" with Error', function (t) {
  t.plan(2)

  var existingObjects = [{id: 'foo', createdBy: 'accountid1'}]
  var hoodie = {
    account: {
      id: 'accountid1',
      on: simple.stub(),
      isSignedIn: simple.stub()
    },
    store: {
      findAll: function () {
        return Promise.resolve(existingObjects)
      },
      reset: simple.stub().rejectWith(new Error('Ooops'))
    },
    connectionStatus: {
      on: simple.stub()
    }
  }

  init(hoodie)
  var preSignInHandler = findEventHandler(hoodie.account.on.calls, 'pre:signin')
  var postSignInHandler = findEventHandler(hoodie.account.on.calls, 'post:signin')

  var preHooks = []
  preSignInHandler({hooks: preHooks})

  var postHooks = []
  postSignInHandler({hooks: postHooks})

  t.is(preHooks.length, 1, 'one pre:signin hook registered')
  t.is(postHooks.length, 1, 'one post:signin hook registered')
  preHooks[0]().then(function () {
    // simulate new account.id
    hoodie.account.id = 'accountid2'
  })
  .then(postHooks[0])
})
<|endoftext|>


<commit_before>$(function() {
    function PSUControlViewModel(parameters) {
        var self = this;

        self.global_settings = parameters[0];
        self.settings = undefined;
        self.loginState = parameters[1];
        self.isPSUOn = ko.observable(undefined);
        self.psu_indicator = undefined;

        self.onAfterBinding = function() {
            self.settings = self.global_settings.settings.plugins.psucontrol;

            self.psu_indicator = $("#psucontrol_indicator");
        };

        self.onDataUpdaterPluginMessage = function(plugin, data) {
            if (plugin != "psucontrol") {
                return;
            }

            self.isPSUOn(data.isPSUOn);

            if (self.isPSUOn()) {
                self.psu_indicator.css('color', '#00FF00');
            } else {
                self.psu_indicator.css('color', '#808080');
            }

        };

        self.togglePSU = function() {
            if (self.isPSUOn()) {
                if (self.settings.enablePowerOffWarningDialog()) {
                    showConfirmationDialog({
                        message: "You are about to turn off the PSU.",
                        onproceed: function() {
                            self.turnPSUOff();
                        }
                    });
                } else {
                    self.turnPSUOff();
                }
            } else {
                self.turnPSUOn();
            }
        };

        self.turnPSUOn = function() {
            $.ajax({
                url: API_BASEURL + "plugin/psucontrol",
                type: "POST",
                dataType: "json",
                data: JSON.stringify({
                    command: "turnPSUOn"
                }),
                contentType: "application/json; charset=UTF-8"
            })
        };

    	self.turnPSUOff = function() {
            $.ajax({
                url: API_BASEURL + "plugin/psucontrol",
                type: "POST",
                dataType: "json",
                data: JSON.stringify({
                    command: "turnPSUOff"
                }),
                contentType: "application/json; charset=UTF-8"
            })
        };
    }

    ADDITIONAL_VIEWMODELS.push([
        PSUControlViewModel,
        ["settingsViewModel", "loginStateViewModel"],
        ["#navbar_plugin_psucontrol"]
    ]);
});
<commit_msg>Fix js error that can occur. (#69)<commit_after>$(function() {
    function PSUControlViewModel(parameters) {
        var self = this;

        self.global_settings = parameters[0];
        self.settings = undefined;
        self.loginState = parameters[1];
        self.isPSUOn = ko.observable(undefined);
        self.psu_indicator = $("#psucontrol_indicator");

        self.onAfterBinding = function() {
            self.settings = self.global_settings.settings.plugins.psucontrol;
        };

        self.onDataUpdaterPluginMessage = function(plugin, data) {
            if (plugin != "psucontrol") {
                return;
            }

            self.isPSUOn(data.isPSUOn);

            if (self.isPSUOn()) {
                self.psu_indicator.css('color', '#00FF00');
            } else {
                self.psu_indicator.css('color', '#808080');
            }

        };

        self.togglePSU = function() {
            if (self.isPSUOn()) {
                if (self.settings.enablePowerOffWarningDialog()) {
                    showConfirmationDialog({
                        message: "You are about to turn off the PSU.",
                        onproceed: function() {
                            self.turnPSUOff();
                        }
                    });
                } else {
                    self.turnPSUOff();
                }
            } else {
                self.turnPSUOn();
            }
        };

        self.turnPSUOn = function() {
            $.ajax({
                url: API_BASEURL + "plugin/psucontrol",
                type: "POST",
                dataType: "json",
                data: JSON.stringify({
                    command: "turnPSUOn"
                }),
                contentType: "application/json; charset=UTF-8"
            })
        };

    	self.turnPSUOff = function() {
            $.ajax({
                url: API_BASEURL + "plugin/psucontrol",
                type: "POST",
                dataType: "json",
                data: JSON.stringify({
                    command: "turnPSUOff"
                }),
                contentType: "application/json; charset=UTF-8"
            })
        };
    }

    ADDITIONAL_VIEWMODELS.push([
        PSUControlViewModel,
        ["settingsViewModel", "loginStateViewModel"],
        ["#navbar_plugin_psucontrol"]
    ]);
});
<|endoftext|>


<commit_before>var gulp = require('gulp');

//For cleaning out the build dir
var clean = require('gulp-clean');

//For processing react and other files into minimized files
var rename = require('gulp-rename');

var react = require('gulp-react');

var gutil = require("gulp-util");
var fs = require("fs");
var include = require('gulp-html-tag-include');

// Delete everything inside the dist directory
gulp.task('clean', function() {
  return gulp.src(['dist/*'], {read: false}).pipe(clean());
});

// Copy all bootstrap files from node_modules/bootstrap/dist:
gulp.task('build_assets', function() {
    gutil.log("copy assets from mock to dist");

    return gulp.src(['mock/impl/css/*','mock/impl/fonts/*','mock/impl/images/*','mock/impl/js/*'],
                    {base: 'mock/impl/'})
        .pipe(gulp.dest('dist'));
});

gulp.task('build_favicon', function() {
  gutil.log("copy favicon dir to dist");

  return gulp.src('tabli-favicon.ico/*')
    .pipe(gulp.dest('dist/favicon'));
});

gulp.task('html-include', ['build_assets'], function() {
    return gulp.src('./src/*.html')
        .pipe(include())
        .pipe(gulp.dest('./dist/'));
});

gulp.task('watch', ['html-include'], function() {
    gulp.watch('./src/**/*.html', ['html-include']);
});

gulp.task('default', ['watch']);<commit_msg>bump manifest version<commit_after>var gulp = require('gulp');

//For cleaning out the build dir
var clean = require('gulp-clean');

//For processing react and other files into minimized files
var rename = require('gulp-rename');

var react = require('gulp-react');

var gutil = require("gulp-util");
var fs = require("fs");
var include = require('gulp-html-tag-include');

// Delete everything inside the dist directory
gulp.task('clean', function() {
  return gulp.src(['dist/*'], {read: false}).pipe(clean());
});

// Copy all bootstrap files from node_modules/bootstrap/dist:
gulp.task('build_assets', function() {
    gutil.log("copy assets from mock to dist");

    return gulp.src(['mock/impl/css/*','mock/impl/fonts/*','mock/impl/images/*','mock/impl/js/*'],
                    {base: 'mock/impl/'})
        .pipe(gulp.dest('dist'));
});

gulp.task('build_favicon', function() {
  gutil.log("copy favicon dir to dist");

  return gulp.src('tabli-favicon.ico/*')
    .pipe(gulp.dest('dist/favicon'));
});

gulp.task('html-include', ['build_assets', 'build_favicon'], function() {
    return gulp.src('./src/*.html')
        .pipe(include())
        .pipe(gulp.dest('./dist/'));
});

gulp.task('watch', ['html-include'], function() {
    gulp.watch('./src/**/*.html', ['html-include']);
});

gulp.task('default', ['watch']);<|endoftext|>


<commit_before>"use strict";

const _ = require('lodash');
const P = require('bluebird');
const Plugin = require('../daemon').Plugin;
const Semaphore = require('../models').Semaphore;

////////////////////////////////////////////////////////////////////////////

const ONE_MINUTE = 60 * 1000;

module.exports = class Janitor extends Plugin {

  /**
   * Lifecycle moment
   * @async
   * @param void
   * @return void
   */
  up () {
    let iterate = () => this.iterate();
    this.interval = setInterval(iterate, this.get('frequency') || ONE_MINUTE);

    setImmediate(iterate);

    return P.resolve();
  }

  /**
   * Lifecycle moment
   * @async
   * @param void
   * @return void
   */
  down () {
    clearInterval(this.interval);
    return P.resolve();
  }

  /**
   * Delete expired Semaphore models, then raise the "cleanup:locks" event
   * on the global event bus.
   * @async
   * @param void
   * @return void
   */
  iterate () {
    let config = this.app('config');
    let log = this.app('log');

    return Semaphore.objects.cleanup(config.opt.lockttl)

    .then((semaphores) => {

      let n = semaphores.length;

      log.info('deleted %s stale locks', n);

      semaphores.forEach((semaphore) => {
        console.log(' ' + semaphore.get('path'));
      });

      this.app('eventbus').emit('locks:cleared', semaphores);
    });
  }

}
<commit_msg>logging<commit_after>"use strict";

const _ = require('lodash');
const P = require('bluebird');
const Plugin = require('../daemon').Plugin;
const Semaphore = require('../models').Semaphore;

////////////////////////////////////////////////////////////////////////////

const ONE_MINUTE = 60 * 1000;

module.exports = class Janitor extends Plugin {

  /**
   * Lifecycle moment
   * @async
   * @param void
   * @return void
   */
  up () {
    let iterate = () => this.iterate();
    this.interval = setInterval(iterate, this.get('frequency') || ONE_MINUTE);

    setImmediate(iterate);

    return P.resolve();
  }

  /**
   * Lifecycle moment
   * @async
   * @param void
   * @return void
   */
  down () {
    clearInterval(this.interval);
    return P.resolve();
  }

  /**
   * Delete expired Semaphore models, then raise the "cleanup:locks" event
   * on the global event bus.
   * @async
   * @param void
   * @return void
   */
  iterate () {
    let config = this.app('config');
    let log = this.app('log');

    return Semaphore.objects.cleanup(config.opt.lockttl)

    .then((semaphores) => {

      let n = semaphores.length;

      log.debug('deleted %s stale locks', n);

      semaphores.forEach((semaphore) => {
        log.info('[RELS] ' + semaphore.get('path'));
      });

      this.app('eventbus').emit('locks:cleared', semaphores);
    });
  }

}
<|endoftext|>


<commit_before>'use strict';

const createLink = (options, ctx) => {
  const { base, format } = options;

  return i => ctx.url_for(i === 1 ? base : base + format.replace('%d', i));
};

function paginatorHelper(options = {}) {
  options = Object.assign({
    base: this.page.base || '',
    current: this.page.current || 0,
    format: `${this.config.pagination_dir}/%d/`,
    total: this.page.total || 1,
    end_size: 1,
    mid_size: 2,
    space: '…',
    next_text: 'Next',
    prev_text: 'Prev',
    prev_next: true
  }, options);

  const {
    current,
    total,
    space,
    transform,
    end_size: endSize,
    mid_size: midSize,
    prev_text: prevText,
    next_text: nextText,
    prev_next: prevNext
  } = options;

  if (!current) return '';

  const currentPage = `<span class="page-number current">${transform ? transform(current) : current}</span>`;

  const link = createLink(options, this);

  function pageLink(i) {
    return `<a class="page-number" href="${link(i)}">${transform ? transform(i) : i}</a>`;
  }

  let result = '';
  // Display the link to the previous page
  if (prevNext && current > 1) {
    result += `<a class="extend prev" rel="prev" href="${link(current - 1)}">${prevText}</a>`;
  }

  if (options.show_all) {
    // Display pages on the left side of the current page
    for (let i = 1; i < current; i++) {
      result += pageLink(i);
    }

    // Display the current page
    result += currentPage;

    // Display pages on the right side of the current page
    for (let i = current + 1; i <= total; i++) {
      result += pageLink(i);
    }
  } else {
    // It's too complicated. May need refactor.
    const leftEnd = current <= endSize ? current - 1 : endSize;
    const rightEnd = total - current <= endSize ? current + 1 : total - endSize + 1;
    const leftMid = current - midSize <= endSize ? leftEnd + 1 : current - midSize;
    const rightMid = current + midSize + endSize > total ? rightEnd - 1 : current + midSize;
    const spaceHtml = `<span class="space">${space}</span>`;

    // Display pages on the left edge
    for (let i = 1; i <= leftEnd; i++) {
      result += pageLink(i);
    }

    // Display spaces between edges and middle pages
    if (space && current - endSize - midSize > 1) {
      result += spaceHtml;
    }

    // Display left middle pages
    if (leftMid > leftEnd) {
      for (let i = leftMid; i < current; i++) {
        result += pageLink(i);
      }
    }

    // Display the current page
    result += currentPage;

    // Display right middle pages
    if (rightMid < rightEnd) {
      for (let i = current + 1; i <= rightMid; i++) {
        result += pageLink(i);
      }
    }

    // Display spaces between edges and middle pages
    if (space && total - endSize - midSize > current) {
      result += spaceHtml;
    }

    // Dispaly pages on the right edge
    for (let i = rightEnd; i <= total; i++) {
      result += pageLink(i);
    }
  }

  // Display the link to the next page
  if (prevNext && current < total) {
    result += `<a class="extend next" rel="next" href="${link(current + 1)}">${nextText}</a>`;
  }

  return result;
}

module.exports = paginatorHelper;
<commit_msg>Create createPageTag function<commit_after>'use strict';

const { htmlTag } = require('hexo-util');

const createLink = (options, ctx) => {
  const { base, format } = options;

  return i => ctx.url_for(i === 1 ? base : base + format.replace('%d', i));
};

const createPageTag = (options, ctx) => {
  const link = createLink(options, ctx);
  const { current, transform } = options;

  return i => {
    if (i === current) {
      return htmlTag('span', { class: 'page-number current' }, transform ? transform(i) : i);
    }
    return htmlTag('a', { class: 'page-number', href: link(i) }, transform ? transform(i) : i);
  };
};

function paginatorHelper(options = {}) {
  options = Object.assign({
    base: this.page.base || '',
    current: this.page.current || 0,
    format: `${this.config.pagination_dir}/%d/`,
    total: this.page.total || 1,
    end_size: 1,
    mid_size: 2,
    space: '…',
    next_text: 'Next',
    prev_text: 'Prev',
    prev_next: true
  }, options);

  const {
    current,
    total,
    space,
    end_size: endSize,
    mid_size: midSize,
    prev_text: prevText,
    next_text: nextText,
    prev_next: prevNext
  } = options;

  if (!current) return '';

  const link = createLink(options, this);

  const pageLink = createPageTag(options, this);

  let result = '';
  // Display the link to the previous page
  if (prevNext && current > 1) {
    result += `<a class="extend prev" rel="prev" href="${link(current - 1)}">${prevText}</a>`;
  }

  if (options.show_all) {
    for (let i = 1; i <= total; i++) {
      result += pageLink(i);
    }
  } else {
    // It's too complicated. May need refactor.
    const leftEnd = current <= endSize ? current - 1 : endSize;
    const rightEnd = total - current <= endSize ? current + 1 : total - endSize + 1;
    const leftMid = current - midSize <= endSize ? leftEnd + 1 : current - midSize;
    const rightMid = current + midSize + endSize > total ? rightEnd - 1 : current + midSize;
    const spaceHtml = `<span class="space">${space}</span>`;

    // Display pages on the left edge
    for (let i = 1; i <= leftEnd; i++) {
      result += pageLink(i);
    }

    // Display spaces between edges and middle pages
    if (space && current - endSize - midSize > 1) {
      result += spaceHtml;
    }

    // Display left middle pages
    if (leftMid > leftEnd) {
      for (let i = leftMid; i < current; i++) {
        result += pageLink(i);
      }
    }

    // Display the current page
    result += pageLink(current);

    // Display right middle pages
    if (rightMid < rightEnd) {
      for (let i = current + 1; i <= rightMid; i++) {
        result += pageLink(i);
      }
    }

    // Display spaces between edges and middle pages
    if (space && total - endSize - midSize > current) {
      result += spaceHtml;
    }

    // Dispaly pages on the right edge
    for (let i = rightEnd; i <= total; i++) {
      result += pageLink(i);
    }
  }

  // Display the link to the next page
  if (prevNext && current < total) {
    result += `<a class="extend next" rel="next" href="${link(current + 1)}">${nextText}</a>`;
  }

  return result;
}

module.exports = paginatorHelper;
<|endoftext|>