[IMP] hr_recruitment: UX improvements
Multi-Application Management and UX improvements. Improve in Recruitment app Job Positions Kanban view; It is more intuitive if list is applications default view; improve the applications form; Task - 2404610 closes odoo/odoo#63946 Related: odoo/enterprise#15750 Signed-off-by: Kevin Baptiste <kba@odoo.com>
This commit is contained in:
@@ -27,6 +27,8 @@ class Job(models.Model):
|
||||
new_application_count = fields.Integer(
|
||||
compute='_compute_new_application_count', string="New Application",
|
||||
help="Number of applications that are new in the flow (typically at first step of the flow)")
|
||||
old_application_count = fields.Integer(
|
||||
compute='_compute_old_application_count', string="Old Application")
|
||||
manager_id = fields.Many2one(
|
||||
'hr.employee', related='department_id.manager_id', string="Department Manager",
|
||||
readonly=True, store=True)
|
||||
@@ -100,6 +102,11 @@ class Job(models.Model):
|
||||
[("job_id", "=", job.id), ("stage_id", "=", job._get_first_stage().id)]
|
||||
)
|
||||
|
||||
@api.depends('application_count', 'new_application_count')
|
||||
def _compute_old_application_count(self):
|
||||
for job in self:
|
||||
job.old_application_count = job.application_count - job.new_application_count
|
||||
|
||||
def _alias_get_creation_values(self):
|
||||
values = super(Job, self)._alias_get_creation_values()
|
||||
values['alias_model_id'] = self.env['ir.model']._get('hr.applicant').id
|
||||
|
||||
@@ -196,7 +196,7 @@ class Applicant(models.Model):
|
||||
application_data_mapped = dict((data['email_from'], data['email_from_count']) for data in application_data)
|
||||
applicants = self.filtered(lambda applicant: applicant.email_from)
|
||||
for applicant in applicants:
|
||||
applicant.application_count = application_data_mapped.get(applicant.email_from, 1) - 1
|
||||
applicant.application_count = application_data_mapped.get(applicant.email_from, 1)
|
||||
(self - applicants).application_count = False
|
||||
|
||||
@api.depends_context('lang')
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
&.o_kanban_ungrouped .o_kanban_record {
|
||||
width: 350px;
|
||||
&:not(.o_kanban_ghost) {
|
||||
height: 180px;
|
||||
height: 160px;
|
||||
@include media-breakpoint-down(sm) {
|
||||
height: 150px;
|
||||
}
|
||||
@@ -31,6 +31,10 @@
|
||||
right: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.text_top_padding{
|
||||
padding-top: 0.375rem;
|
||||
}
|
||||
}
|
||||
|
||||
.o_kanban_record_subtitle {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<field name="manager_id"/>
|
||||
<field name="state"/>
|
||||
<field name="user_id"/>
|
||||
<field name="application_count"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="#{!selection_mode ? kanban_color(record.color.raw_value) : ''}">
|
||||
@@ -60,9 +61,14 @@
|
||||
</t>
|
||||
<t t-if="record.state.raw_value == 'open'">
|
||||
<div class="row">
|
||||
<div class="col-12 o_kanban_primary_left">
|
||||
<div class="col-7 o_kanban_primary_left">
|
||||
<button class="btn btn-secondary" name="set_recruit" type="object">Start Recruitment</button>
|
||||
</div>
|
||||
<t t-if="record.old_application_count.raw_value != 0">
|
||||
<div class="col-5 text_top_padding">
|
||||
<field name="old_application_count"/> Applications<br/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<div name="kanban_boxes" class="row o_recruitment_kanban_boxes">
|
||||
@@ -81,6 +87,7 @@
|
||||
<div role="menuitem"><a t-if="record.state.raw_value == 'recruit'" name="set_open" type="object">Recruitment Done</a></div>
|
||||
<div role="menuitem"><a t-if="record.state.raw_value == 'open'" name="set_recruit" type="object">Start recruitment</a></div>
|
||||
<div role="menuitem"><a t-if="widget.editable" name="edit_job" type="edit">Edit</a></div>
|
||||
<div role="menuitem" name="applications_action"><a t-if="record.state.raw_value == 'open' and record.application_count.raw_value != 0" name="%(action_hr_job_applications)d" type="action">Applications</a></div>
|
||||
</div>
|
||||
<div role="menuitem" aria-haspopup="true">
|
||||
<ul class="oe_kanban_colorpicker" data-field="color" role="menu"/>
|
||||
|
||||
@@ -27,16 +27,18 @@
|
||||
<tree string="Applicants" multi_edit="1" sample="1">
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
<field name="last_stage_id" invisible="1"/>
|
||||
<field name="create_date" readonly="1" widget="date" optional="show"/>
|
||||
<field name="date_last_stage_update" invisible="1"/>
|
||||
<field name="partner_name" readonly="1"/>
|
||||
<field name="job_id"/>
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="priority" widget="priority" optional="show"/>
|
||||
<field name="partner_mobile" widget="phone" readonly="1" optional="show"/>
|
||||
<field name="categ_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="show"/>
|
||||
<field name="user_id" widget="many2one_avatar_user" optional="show"/>
|
||||
<field name="create_date" readonly="1" widget="date" optional="show"/>
|
||||
<field name="partner_phone" widget="phone" readonly="1" optional="hide"/>
|
||||
<field name="email_from" readonly="1" optional="hide"/>
|
||||
<field name="job_id"/>
|
||||
<field name="categ_ids" widget="many2many_tags" options="{'color_field': 'color'}" optional="show"/>
|
||||
<field name="priority" widget="priority" optional="show"/>
|
||||
<field name="medium_id" optional="hide"/>
|
||||
<field name="source_id" readonly="1" optional="hide"/>
|
||||
<field name="salary_expected" optional="hide"/>
|
||||
@@ -44,9 +46,7 @@
|
||||
<field name="type_id" invisible="1"/>
|
||||
<field name="availability" optional="hide"/>
|
||||
<field name="department_id" invisible="context.get('invisible_department', True)" readonly="1"/>
|
||||
<field name="user_id" widget="many2one_avatar_user" optional="show"/>
|
||||
<field name="company_id" groups="base.group_multi_company" readonly="1" optional="hide"/>
|
||||
<field name="stage_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -86,8 +86,8 @@
|
||||
icon="fa-pencil"
|
||||
type="object"
|
||||
context="{'active_test': False}"
|
||||
attrs="{'invisible': [('application_count', '=', 0)]}">
|
||||
<field name="application_count" widget="statinfo" string="Other Applications"/>
|
||||
attrs="{'invisible': [('application_count', '<' , 2)]}">
|
||||
<field name="application_count" widget="statinfo" string="Applications"/>
|
||||
</button>
|
||||
<button name="action_makeMeeting" class="oe_stat_button" icon="fa-calendar" type="object"
|
||||
>
|
||||
@@ -106,7 +106,7 @@
|
||||
<field name="legend_done" invisible="1"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
<h1><field name="name" placeholder="e.g. Sales Manager 2 year experience"/></h1>
|
||||
<h2 class="o_row">
|
||||
<div>
|
||||
<label for="partner_name" class="oe_edit_only"/>
|
||||
@@ -125,7 +125,7 @@
|
||||
<field name="type_id" placeholder="Degree"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="categ_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}"/>
|
||||
<field name="categ_ids" placeholder="Tags" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}"/>
|
||||
<field name="user_id" domain="[('share', '=', False)]"/>
|
||||
<field name="date_closed" attrs="{'invisible': [('date_closed', '=', False)]}" />
|
||||
<field name="priority" widget="priority"/>
|
||||
@@ -154,8 +154,11 @@
|
||||
<field name="emp_id" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="Application Summary"/>
|
||||
<field name="description" placeholder="Feedback of interviews..."/>
|
||||
<notebook>
|
||||
<page string="Application Summary">
|
||||
<field name="description" placeholder="Motivations..."/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
@@ -390,7 +393,7 @@
|
||||
<record model="ir.actions.act_window" id="action_hr_job_applications">
|
||||
<field name="name">Applications</field>
|
||||
<field name="res_model">hr.applicant</field>
|
||||
<field name="view_mode">kanban,tree,form,graph,calendar,pivot</field>
|
||||
<field name="view_mode">kanban,tree,form,graph,calendar,pivot,activity</field>
|
||||
<field name="search_view_id" ref="hr_applicant_view_search_bis"/>
|
||||
<field name="context">{'search_default_job_id': [active_id], 'default_job_id': active_id}</field>
|
||||
<field name="help" type="html">
|
||||
@@ -599,14 +602,14 @@
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="action_hr_sec_kanban_view_act_job">
|
||||
<field name="sequence" eval="0"/>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="view_id" ref="hr_kanban_view_applicant"/>
|
||||
<field name="act_window_id" ref="crm_case_categ0_act_job"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window.view" id="action_hr_sec_tree_view_act_job">
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="sequence" eval="0"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="view_id" ref="crm_case_tree_view_job"/>
|
||||
<field name="act_window_id" ref="crm_case_categ0_act_job"/>
|
||||
|
||||
@@ -32,9 +32,10 @@
|
||||
<field name="module_hr_recruitment_survey"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_hr_recruitment_survey" string="Interview Forms"/>
|
||||
<label for="module_hr_recruitment_survey" string="Send Interview Survey"/>
|
||||
<div class="text-muted">
|
||||
Use interview forms during recruitment process
|
||||
Send an Interview Survey to the applicant during
|
||||
the recruitment process
|
||||
</div>
|
||||
<div class="content-group" id="interview_forms"/>
|
||||
</div>
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import hr_job
|
||||
from . import hr_applicant
|
||||
from . import survey_invite
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class Applicant(models.Model):
|
||||
@@ -8,19 +9,26 @@ class Applicant(models.Model):
|
||||
|
||||
survey_id = fields.Many2one('survey.survey', related='job_id.survey_id', string="Survey", readonly=True)
|
||||
response_id = fields.Many2one('survey.user_input', "Response", ondelete="set null")
|
||||
|
||||
def action_start_survey(self):
|
||||
self.ensure_one()
|
||||
# create a response and link it to this applicant
|
||||
if not self.response_id:
|
||||
response = self.survey_id._create_answer(partner=self.partner_id)
|
||||
self.response_id = response.id
|
||||
else:
|
||||
response = self.response_id
|
||||
# grab the token of the response and start surveying
|
||||
return self.survey_id.action_start_survey(answer=response)
|
||||
response_state = fields.Selection(related='response_id.state', readonly=True)
|
||||
|
||||
def action_print_survey(self):
|
||||
""" If response is available then print this response otherwise print survey form (print template of the survey) """
|
||||
self.ensure_one()
|
||||
return self.survey_id.action_print_survey(answer=self.response_id)
|
||||
|
||||
def action_send_survey(self):
|
||||
self.ensure_one()
|
||||
|
||||
# if an applicant does not already has associated partner_id create it
|
||||
if not self.partner_id:
|
||||
if not self.partner_name:
|
||||
raise UserError(_('You must define a Contact Name for this applicant.'))
|
||||
self.partner_id = self.env['res.partner'].create({
|
||||
'is_company': False,
|
||||
'type': 'private',
|
||||
'name': self.partner_name,
|
||||
'email': self.email_from,
|
||||
'phone': self.partner_phone,
|
||||
'mobile': self.partner_mobile
|
||||
})
|
||||
return self.survey_id.with_context(default_partner_ids=self.partner_id.ids, active_model='hr.applicant', active_id=self.id).action_send_survey()
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, _
|
||||
|
||||
|
||||
class SurveyInvite(models.TransientModel):
|
||||
_inherit = "survey.invite"
|
||||
|
||||
applicant_id = fields.Many2one('hr.applicant', string='Applicant', default=lambda self: self.env.context.get('active_id', None))
|
||||
|
||||
def action_invite(self):
|
||||
self.ensure_one()
|
||||
|
||||
if not self.applicant_id.response_id:
|
||||
response = self.applicant_id.survey_id._create_answer(partner=self.applicant_id.partner_id)
|
||||
self.applicant_id.response_id = response.id
|
||||
|
||||
body = _('The survey has been sent to "%s".', self.applicant_id.partner_name)
|
||||
self.applicant_id.message_post(body=body)
|
||||
|
||||
return super().action_invite()
|
||||
|
||||
|
||||
class SurveyUserInput(models.Model):
|
||||
_inherit = "survey.user_input"
|
||||
|
||||
applicant_id = fields.One2many('hr.applicant', 'response_id', string='Applicant')
|
||||
|
||||
def _mark_done(self):
|
||||
odoobot = self.env.ref('base.partner_root')
|
||||
for user_input in self:
|
||||
if user_input.applicant_id:
|
||||
body = _('The applicant "%s" has finished the survey.', user_input.applicant_id.partner_name)
|
||||
user_input.applicant_id.message_post(body=body, author_id=odoobot.id)
|
||||
return super()._mark_done()
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import common
|
||||
from odoo.tests import common, Form
|
||||
|
||||
|
||||
class TestRecruitmentSurvey(common.SingleTransactionCase):
|
||||
@@ -14,26 +14,50 @@ class TestRecruitmentSurvey(common.SingleTransactionCase):
|
||||
cls.department_admins = cls.env['hr.department'].create({'name': 'Admins'})
|
||||
cls.survey_sysadmin = cls.env['survey.survey'].create({'title': 'Questions for Sysadmin job offer'})
|
||||
|
||||
# We need this, so that when we send survey, we don't get an error
|
||||
cls.question_ft = cls.env['survey.question'].create({
|
||||
'title': 'Test Free Text',
|
||||
'survey_id': cls.survey_sysadmin.id,
|
||||
'sequence': 2,
|
||||
'question_type': 'text_box',
|
||||
})
|
||||
|
||||
cls.job = cls.env['hr.job'].create({
|
||||
'name': 'Technical worker',
|
||||
'survey_id': cls.survey_sysadmin.id,
|
||||
})
|
||||
cls.job_sysadmin = cls.env['hr.applicant'].create({
|
||||
'name': 'Technical worker',
|
||||
'partner_name': 'Jane Doe',
|
||||
'email_from': 'customer@example.com',
|
||||
'department_id': cls.department_admins.id,
|
||||
'description': 'A nice Sys Admin job offer !',
|
||||
'job_id': cls.job.id,
|
||||
})
|
||||
|
||||
def test_start_survey(self):
|
||||
def test_send_survey(self):
|
||||
# We ensure that response is False because we don't know test order
|
||||
self.job_sysadmin.response_id = False
|
||||
action_start = self.job_sysadmin.action_start_survey()
|
||||
self.assertEqual(action_start['type'], 'ir.actions.act_url')
|
||||
Answer = self.env['survey.user_input']
|
||||
answers = Answer.search([('survey_id', '=', self.survey_sysadmin.id)])
|
||||
answers.unlink()
|
||||
|
||||
self.survey_sysadmin.write({'access_mode': 'public', 'users_login_required': False})
|
||||
action = self.job_sysadmin.action_send_survey()
|
||||
|
||||
invite_form = Form(self.env[action['res_model']].with_context({
|
||||
'active_id': self.job_sysadmin.id,
|
||||
**action['context'],
|
||||
}))
|
||||
invite = invite_form.save()
|
||||
invite.action_invite()
|
||||
|
||||
self.assertNotEqual(self.job_sysadmin.response_id.id, False)
|
||||
self.assertIn(self.job_sysadmin.response_id.access_token, action_start['url'])
|
||||
action_start_with_response = self.job_sysadmin.action_start_survey()
|
||||
self.assertEqual(action_start_with_response, action_start)
|
||||
answers = Answer.search([('survey_id', '=', self.survey_sysadmin.id)])
|
||||
self.assertEqual(len(answers), 1)
|
||||
self.assertEqual(
|
||||
set(answers.mapped('email')),
|
||||
set([self.job_sysadmin.email_from]))
|
||||
|
||||
def test_print_survey(self):
|
||||
# We ensure that response is False because we don't know test order
|
||||
|
||||
@@ -17,19 +17,10 @@
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="inherit_id" ref="hr_recruitment.hr_applicant_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='archive_applicant']" position="before">
|
||||
<button name="action_send_survey" string="SEND INTERVIEW" type="object"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_makeMeeting']" position="after">
|
||||
<button name="action_start_survey"
|
||||
class="oe_stat_button"
|
||||
icon="fa-user"
|
||||
type="object"
|
||||
help="Answer related job question"
|
||||
context="{'survey_id': survey_id}"
|
||||
attrs="{'invisible':[('survey_id','=',False)]}">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">Start</span>
|
||||
<span class="o_stat_text">Interview</span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="action_print_survey"
|
||||
class="oe_stat_button"
|
||||
icon="fa-pencil-square-o"
|
||||
@@ -45,6 +36,7 @@
|
||||
<xpath expr="//field[@name='job_id']" position="before">
|
||||
<field name="survey_id" invisible="1"/>
|
||||
<field name="response_id" invisible="1"/>
|
||||
<field name="response_state" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
<xpath expr="//field[@name='manager_id']" position="after">
|
||||
<field name="survey_id"/>
|
||||
</xpath>
|
||||
<xpath expr='//a[@name="edit_job"]' position="after">
|
||||
<a t-if="record.survey_id.raw_value" name="action_print_survey" type="object" title="Display Interview Form">Preview Interview</a>
|
||||
<a t-if="!record.survey_id.raw_value" name="action_new_survey" type="object" title="Create Interview Form">Create Interview Form</a>
|
||||
<xpath expr='//div[@name="applications_action"]' position="after">
|
||||
<div role="menuitem">
|
||||
<a t-if="record.survey_id.raw_value" name="action_print_survey" type="object" title="Display Interview Form">Preview Interview</a>
|
||||
<a t-if="!record.survey_id.raw_value" name="action_new_survey" type="object" title="Create Interview Form">Create Interview Form</a>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<div id="interview_forms" position="replace">
|
||||
<div class="content-group">
|
||||
<div class="mt8">
|
||||
<button name="%(survey.action_survey_form)d" icon="fa-arrow-right" type="action" string="Interview Forms" class="btn-link"/>
|
||||
<button name="%(survey.action_survey_form)d" icon="fa-arrow-right" type="action" string="Interview Survey" class="btn-link"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -558,7 +558,6 @@ class Survey(http.Controller):
|
||||
return self._redirect_with_error(access_data, access_data['validity_code'])
|
||||
|
||||
survey_sudo, answer_sudo = access_data['survey_sudo'], access_data['answer_sudo']
|
||||
|
||||
return request.render('survey.survey_page_print', {
|
||||
'is_html_empty': is_html_empty,
|
||||
'review': review,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools.translate import html_translate
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ class Applicant(models.Model):
|
||||
|
||||
def website_form_input_filter(self, request, values):
|
||||
if 'partner_name' in values:
|
||||
values.setdefault('name', '%s\'s Application' % values['partner_name'])
|
||||
applicant_job = self.env['hr.job'].sudo().search([('id', '=', values['job_id'])]).name if 'job_id' in values else False
|
||||
name = '%s - %s' % (values['partner_name'], applicant_job) if applicant_job else _("%s's Application", values['partner_name'])
|
||||
values.setdefault('name', name)
|
||||
if values.get('job_id'):
|
||||
stage = self.env['hr.recruitment.stage'].sudo().search([
|
||||
('fold', '=', False),
|
||||
|
||||
@@ -11,11 +11,22 @@
|
||||
<span class="o_recruitment_purple">Published</span>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_primary')]" position="replace">
|
||||
<field name="website_url" invisible="1"/>
|
||||
<div class="o_primary col-11">
|
||||
<span><a t-attf-href="#{record.website_url.raw_value}" class="mr-2">
|
||||
<t t-esc="record.name.value"/></a>
|
||||
</span>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='kanban_boxes']/div/div[1]" position="inside">
|
||||
<field name="website_url" invisible="1"/>
|
||||
<span>
|
||||
<a t-attf-href="#{record.website_url.raw_value}" class="mr-2">Job Description</a>
|
||||
<field name="is_published" widget='boolean_toggle'/>
|
||||
<span><t t-if="record.no_of_recruitment.raw_value != 0">
|
||||
<span class="mr-2">
|
||||
<t t-if="record.is_published.raw_value">Published</t>
|
||||
<t t-else="">Not published</t>
|
||||
</span>
|
||||
<field name="is_published" widget='boolean_toggle'/></t>
|
||||
</span>
|
||||
</xpath>
|
||||
</field>
|
||||
@@ -29,7 +40,7 @@
|
||||
<xpath expr="//div[hasclass('o_link_trackers')]" position="replace">
|
||||
<div class="o_link_trackers col-6">
|
||||
<a role="button" name="%(hr_recruitment.action_hr_job_sources)d" type="action" class="btn btn-sm py-0">
|
||||
<span title='Link Trackers'><i class='fa fa-lg fa-link' role="img" aria-label="Link Trackers"/></span>
|
||||
<span title='Link Trackers'><i class='fa fa-lg fa-link' role="img" aria-label="Link Trackers"/></span>
|
||||
</a>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
Reference in New Issue
Block a user