Merge branch 'develop' into feature/docker

This commit is contained in:
Artyom Kravchenko 2021-04-08 15:45:43 +03:00
commit 0e6f016d1e
15 changed files with 304 additions and 154 deletions

View File

@ -47,9 +47,79 @@ pip install -r requirements.txt
./manage.py loaddata data.json ./manage.py loaddata data.json
./manage.py runserver ./manage.py runserver
``` ```
Создать токен
Указать почту и токен в окружении ##ZenDesk Access Controller instruction for eng
##Перед запуском для тестирования:
Убедитесь, что вы зарегистрированы в песочнице ZenDesk, у вас назначена организация (SYSTEM)
Для админов ZenDesk дополнительно - создайте токен доступа в ZenDesk
При запуске в Docker убедитесь что папка, которая будет служить хранилищем для БД, открыта на запись и чтение
##Запуск на локальной машине:
скопировать репозиторий на локальную машину
перейти в папку приложения
активировать вирутальное окружение
выполнить команду pip install -r requirements.txt
в вирутальное окружение добавить следующие переменные :
ACCESS_CONTROLLER_API_EMAIL={EMAIL} - почта админа в ZenDesk
ACCESS_CONTROLLER_API_PASSWORD={PASSWORD} - пароль админа ZenDesk
ACCESS_CONTROLLER_API_TOKEN={API_TOKEN} - API токен зендеск
ZD_DOMAIN={DOMAIN} - домен ZenDesk
ENG_CROLE_ID={ENGINEER_CUSTOM_ROLE_ID} - id роли инженера( custom_role_id сотрдника смены)
LA_CROLE_ID={LIGHT_AGENT_CUSTOM_ROLE_ID} - id роли легкого агента (custom_role_id роли -легкий агент)
EMPL_GROUP={EMPLOYEE_GROUP_NAME} - имя группы которой принадлежат сотрудники ССКС
BUF_GROUP={BUFFER_GROUP_NAME} - имя буферной группы для передачи смен(через нее происходит управление тикетами)
ST_EMAIL={SOLVED_TICKETS_EMAIL} - почта на которую будут переназначятся закрытые тикеты
LICENSE_NO={LICENSE_NO} - количество лицензий, отображаемых как доступные в приложении
SHIFTH={SHIFT_HOURS} - количество часов в рабочей смене (нужно для статистики, пока не реализовано но требует указания значения)
выполнить команду python manage.py makemigrations
выполнить команду python manage.py migrate
запустить приложение командой python manage.py runserver (можно указать в параметрах для файла manage.py)
перейти по ссылке в консоли (вероятнее всего откроется по адресу http://127.0.0.1:8000/)
##Запуск в Docker:
Требуется установленный и настроеный Docker
скопировать репозиторий на локальную машину
в командной строке перейти в папку проекта
выполнить команду docker build .
выполнить команду docker images (нам нужен id созданного образа)
выполнить команду docker run -d -p 8000:8000 -e ACCESS_CONTROLLER_API_EMAIL={EMAIL} -e ACCESS_CONTROLLER_API_PASSWORD={PASSWORD}
...(перечисляем все параметры виртуального окружени разделяя их -e) -v {абсолютный путь к папке, в которой будет размещена база}:/zendesk-access-controller/db {id образа докера}
открываем запущеный контейнер в браузере (можно перейти по ссылке http://localhost:8000/)
##Запуск с тестовыми юзерами:
На локальной машине - перед запуском команды python manage.py runserver выполнить команду python manage.py loaddata data.json
Это создаст тестового админа и тестового пользователя в приложении для песочницы ZenDesk. Админ - admin@gmail.com / zendeskadmin , пользователь - 123@test.ru / zendeskuser .
Не сработает если домен песочницы отличается от ngenix1612197338 (на другом домене нужно будет создать сначала пользователей в песочнице с правами админа и легкого агента
с этими же почтами, назначить им организацию (SYSTEM))
##Параметры тестовой песочницы:
ACCESS_CONTROLLER_API_EMAIL={EMAIL} - почта админа в ZenDesk - взять у роководителя(если вы не админ)
ACCESS_CONTROLLER_API_PASSWORD={PASSWORD} - пароль админа ZenDesk - взять у роководителя(если вы не админ)
ACCESS_CONTROLLER_API_TOKEN={API_TOKEN} - API токен зендеск - взять у роководителя(если вы не админ)
ZD_DOMAIN=ngenix1612197338
ENG_CROLE_ID=360005209000
LA_CROLE_ID=360005208980
EMPL_GROUP=Поддержка
BUF_GROUP=Сменная группа
ST_EMAIL=d.krikov@ngenix.net
LICENSE_NO=3
SHIFTH=12
## Read more ## Read more
- Zenpy: [http://docs.facetoe.com.au](http://docs.facetoe.com.au) - Zenpy: [http://docs.facetoe.com.au](http://docs.facetoe.com.au)

View File

@ -9,8 +9,9 @@ https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
import os import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'access_controller.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'access_controller.settings')
from django.core.asgi import get_asgi_application
application = get_asgi_application() application = get_asgi_application()

View File

@ -24,7 +24,7 @@ SECRET_KEY = 'v1i_fb$_jf2#1v_lcsbu&eon4u-os0^px=s^iycegdycqy&5)6'
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = ['127.0.0.1']
# Application definition # Application definition

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -381,6 +381,7 @@ class StatisticData:
:param statistic: Интервалы работы пользователя :param statistic: Интервалы работы пользователя
:type statistic: :class:`dict` :type statistic: :class:`dict`
""" """
def __init__(self, start_date, end_date, user_email, stat=None): def __init__(self, start_date, end_date, user_email, stat=None):
self.display = None self.display = None
self.interval = None self.interval = None
@ -543,31 +544,51 @@ class StatisticData:
first_log, last_log = self.data[0], self.data[len(self.data) - 1] first_log, last_log = self.data[0], self.data[len(self.data) - 1]
if first_log.old_role == ROLES['engineer']: if first_log.old_role == ROLES['engineer']:
self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds() self.prev_engineer_logic(first_log)
if last_log.new_role == ROLES['engineer']: # TODO отдельная функция if last_log.new_role == ROLES['engineer']:
self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1)) self.post_engineer_logic(last_log)
if last_log.change_time.date() == timezone.now().date():
self.statistic[last_log.change_time.date()] += (
get_timedelta(None, timezone.now().time()) - get_timedelta(last_log)
).total_seconds()
else:
self.statistic[last_log.change_time.date()] += (
timedelta(days=1) - get_timedelta(last_log)).total_seconds()
if self.end_date == timezone.now().date():
self.statistic[self.end_date] = get_timedelta(None, timezone.now().time()).total_seconds()
for log_index in range(len(self.data) - 1): # TODO отдельная функция for log_index in range(len(self.data) - 1):
if self.data[log_index].new_role == ROLES['engineer']: if self.data[log_index].new_role == ROLES['engineer']:
current_log, next_log = self.data[log_index], self.data[log_index + 1] self.engineer_logic(log_index)
if current_log.change_time.date() != next_log.change_time.date():
self.statistic[current_log.change_time.date()] += ( def engineer_logic(self, log_index):
timedelta(days=1) - get_timedelta(current_log)).total_seconds() """
self.statistic[next_log.change_time.date()] += get_timedelta(next_log).total_seconds() Функция обрабатывает основную часть работы инженера
self.fill_daterange(current_log.change_time.date() + timedelta(days=1), next_log.change_time.date()) """
else: current_log, next_log = self.data[log_index], self.data[log_index + 1]
elapsed_time = next_log.change_time - current_log.change_time if current_log.change_time.date() != next_log.change_time.date():
self.statistic[current_log.change_time.date()] += elapsed_time.total_seconds() self.statistic[current_log.change_time.date()] += (
timedelta(days=1) - get_timedelta(current_log)).total_seconds()
self.statistic[next_log.change_time.date()] += get_timedelta(next_log).total_seconds()
self.fill_daterange(current_log.change_time.date() + timedelta(days=1), next_log.change_time.date())
else:
elapsed_time = next_log.change_time - current_log.change_time
self.statistic[current_log.change_time.date()] += elapsed_time.total_seconds()
def post_engineer_logic(self, last_log):
"""
Функция обрабатывает случай, когда нам изветсно что инженер работал и после диапазона
"""
self.fill_daterange(last_log.change_time.date() + timedelta(days=1), self.end_date + timedelta(days=1))
if last_log.change_time.date() == timezone.now().date():
self.statistic[last_log.change_time.date()] += (
get_timedelta(None, timezone.now().time()) - get_timedelta(last_log)
).total_seconds()
else:
self.statistic[last_log.change_time.date()] += (
timedelta(days=1) - get_timedelta(last_log)).total_seconds()
if self.end_date == timezone.now().date():
self.statistic[self.end_date] = get_timedelta(None, timezone.now().time()).total_seconds()
def prev_engineer_logic(self, first_log):
"""
Функция обрабатывает случай, когда нам изветсно что инженер начал работу до диапазона
"""
self.fill_daterange(max(User.objects.get(email=self.email).date_joined.date(), self.start_date),
first_log.change_time.date())
self.statistic[first_log.change_time.date()] += get_timedelta(first_log).total_seconds()
def fill_daterange(self, first: date, last: date, val: int = 24 * 3600) -> dict: def fill_daterange(self, first: date, last: date, val: int = 24 * 3600) -> dict:
""" """
@ -576,7 +597,6 @@ class StatisticData:
:param first: Начальная дата интервала :param first: Начальная дата интервала
:param last: Последняя дата интервала :param last: Последняя дата интервала
:param val: Количество секунд в одном дне :param val: Количество секунд в одном дне
:return: Статистику пользователя с указанным количеством секунд в заданных днях
""" """
for day in daterange(first, last): for day in daterange(first, last):
self.statistic[day] = val self.statistic[day] = val
@ -584,8 +604,6 @@ class StatisticData:
def clear_statistic(self) -> dict: def clear_statistic(self) -> dict:
""" """
Функция осуществляет обновление всех дней. Функция осуществляет обновление всех дней.
:return: Статистику пользователя с количеством рабочих секунд = 0
""" """
self.statistic.clear() self.statistic.clear()
self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0) self.fill_daterange(self.start_date, self.end_date + timedelta(days=1), 0)

View File

@ -2,7 +2,6 @@ from django import forms
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django_registration.forms import RegistrationFormUniqueEmail from django_registration.forms import RegistrationFormUniqueEmail
from access_controller.settings import ZENDESK_ROLES
from main.models import UserProfile from main.models import UserProfile
@ -14,6 +13,7 @@ class CustomRegistrationForm(RegistrationFormUniqueEmail):
:param visible_fields.email: Поле для ввода email, зарегистрированного на Zendesk :param visible_fields.email: Поле для ввода email, зарегистрированного на Zendesk
:type visible_fields.email: :class:`django_registration.forms.RegistrationFormUniqueEmail` :type visible_fields.email: :class:`django_registration.forms.RegistrationFormUniqueEmail`
""" """
def __init__(self, *args, **kwargs) -> RegistrationFormUniqueEmail: def __init__(self, *args, **kwargs) -> RegistrationFormUniqueEmail:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
for visible in self.visible_fields(): for visible in self.visible_fields():
@ -70,6 +70,16 @@ class CustomAuthenticationForm(AuthenticationForm):
} }
INTERVAL_CHOICES = [
('days', 'Дни'),
('months', 'Месяцы')
]
DISPLAY_CHOICES = [
('hours', 'Часы'),
('days', 'Дни/Смены')
]
class StatisticForm(forms.Form): class StatisticForm(forms.Form):
""" """
Форма отображения интервалов работы пользователя. Форма отображения интервалов работы пользователя.
@ -87,26 +97,47 @@ class StatisticForm(forms.Form):
""" """
email = forms.EmailField( email = forms.EmailField(
label='Электроная почта', label='Электроная почта',
) widget=forms.EmailInput(
interval = forms.CharField( # TODO: Переделать под html страницу
label='Интервал работы',
)
display_format = forms.CharField( # TODO: Переделать под html страницу
label='Формат отображения',
)
range_start = forms.DateField( # TODO: Переделать под html страницу
label='Начало диапазона',
widget=forms.DateInput(
attrs={ attrs={
'type': 'date', 'placeholder': 'example@ngenix.ru',
'class': 'form-control',
'style': 'background-color:#f2f2f2;'
} }
), ),
) )
range_end = forms.DateField( # TODO: Переделать под html страницу interval = forms.ChoiceField(
label='Конец диапазона', label='Выберите интервалы времени работы',
choices=INTERVAL_CHOICES,
widget=forms.RadioSelect(
attrs={
'class': 'btn-check',
}
)
)
display_format = forms.ChoiceField(
label='Выберите формат отображения',
choices=DISPLAY_CHOICES,
widget=forms.RadioSelect(
attrs={
'class': 'btn-check',
}
)
)
range_start = forms.DateField(
label='Начало статистики',
widget=forms.DateInput( widget=forms.DateInput(
attrs={ attrs={
'type': 'date', 'type': 'date',
'class': 'btn btn-secondary text-primary bg-white',
}
),
)
range_end = forms.DateField(
label='Конец статистики',
widget=forms.DateInput(
attrs={
'type': 'date',
'class': 'btn btn-secondary text-primary bg-white',
} }
), ),
) )

View File

@ -4,6 +4,8 @@ from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone from django.utils import timezone
from access_controller.settings import ZENDESK_ROLES
class UserProfile(models.Model): class UserProfile(models.Model):
""" """
@ -23,6 +25,14 @@ class UserProfile(models.Model):
image = models.URLField(null=True, blank=True, help_text='Аватарка') image = models.URLField(null=True, blank=True, help_text='Аватарка')
name = models.CharField(default='None', max_length=100, help_text='Имя пользователя на нашем сайте') name = models.CharField(default='None', max_length=100, help_text='Имя пользователя на нашем сайте')
@property
def zendesk_role(self):
id = self.custom_role_id
for role, r_id in ZENDESK_ROLES.items():
if r_id == id:
return role
return 'UNDEFINED'
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs): def create_user_profile(sender, instance, created, **kwargs):

View File

@ -13,11 +13,9 @@ class UserSerializer(serializers.HyperlinkedModelSerializer):
class ProfileSerializer(serializers.HyperlinkedModelSerializer): class ProfileSerializer(serializers.HyperlinkedModelSerializer):
""" """Сериализатор для модели профиля пользователя"""
Класс serializer для модель профиля пользователя.
"""
user = UserSerializer() user = UserSerializer()
class Meta: class Meta:
model = UserProfile model = UserProfile
fields = ['user', 'id', 'role', 'name'] fields = ['user', 'id', 'name', 'zendesk_role']

View File

@ -30,14 +30,6 @@
<div class="row justify-content-center new-section"> <div class="row justify-content-center new-section">
{% block hidden_form %}
<div style="display: none">
{% for field in form.users %}
{{ field.tag }}
{% endfor %}
</div>
{% endblock %}
<div class="col-10"> <div class="col-10">
<h6 class="table-title">Список сотрудников</h6> <h6 class="table-title">Список сотрудников</h6>
@ -50,25 +42,11 @@
<th>Role</th> <th>Role</th>
<th>Checked</th> <th>Checked</th>
</thead> </thead>
<tbody id="tbody">
<tbody id="old_tbody">
{% for user in users %}
<tr>
<td><a href="#">{{ user.name }}</a></td>
<td>{{ user.user.email }}</td>
<td>{% if user.custom_role_id == ZENDESK_ROLES.engineer %}
engineer
{% elif user.custom_role_id == ZENDESK_ROLES.light_agent %}
light_agent
{% endif %}
</td>
<td class="checkbox_field"></td>
</tr>
{% endfor %}
</tbody> </tbody>
<tbody id="new_tbody"></tbody>
</table> </table>
{% endblock%} <p id="loading">Данные загружаются...</p>
{% endblock %}
</div> </div>
</div> </div>
@ -96,7 +74,9 @@
</div> </div>
</div> </div>
{% endblock %}
{% block buttons %}
<div class="col-5"> <div class="col-5">
<button type="submit" name="engineer" class="request-acess-button default-button"> <button type="submit" name="engineer" class="request-acess-button default-button">
@ -107,8 +87,9 @@
Назначить выбранных на роль легкого агента Назначить выбранных на роль легкого агента
</button> </button>
</div> </div>
{% endblock %}
</div> </div>
{% endblock %}
</form> </form>
{% endblock %} {% endblock %}

View File

@ -1,49 +0,0 @@
{% extends 'base/base.html' %}
{% load static %}
{% block title %}{{ pagename }}{% endblock %}
{% block heading %} Пример страницы статистики(палками не бейти плиз){% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'main/css/work.css' %}">
{% endblock %}
{% block content %}
<div>
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
{{field.label}}
{{field}}
<br>
{% endfor %}
<input type="submit">
</form>
<ul>
{% for error in errors %}
<li><span class="badge bg-danger">{{error}}</span></li>
{% endfor %}
</ul>
{%if form.errors%}
<ul>
{% for field, errors in form.errors.items %}
{% for error in errors %}
<li><span class="badge bg-danger">{{error}}</span></li>
{% endfor %}
{% endfor %}
</ul>
{%endif%}
<ul>
{% for warning in warnings %}
<li><span class="badge bg-warning">{{warning}}</span></li>
{% endfor %}
</ul>
{% for key,val in log_stats.items %}
<h3>{{key}} <b>|</b> {{val}}</h3>
<br>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,106 @@
{% extends 'base/base.html' %}
{% load static %}
{% block title %}{{ pagename }}{% endblock %}
{% block heading %} Страницы просмотра статистики{% endblock %}
{% block content%}
<div class="mt-5">
<div class="container-fluid" style="font-size:2rem">
<form method="post">
{% csrf_token %}
<div class="row g-3">
<div class="col-auto">
{{ form.email.label }}
</div>
<div class="col-auto mt-4">
{{ form.email }}
</div>
</div>
<div class="row g-3 mt-4">
<div class="col-auto">
{{ form.interval.label }}
</div>
<div class="col-auto">
{% for radio in form.interval%}
{{ radio.tag }}
<label class="btn btn-secondary text-primary bg-white" for="{{ radio.id_for_label }}">
{{ radio.choice_label }}
</label>
{% endfor %}
</div>
</div>
<div class="row g-3 mt-4">
<div class="col-auto">
{{ form.display_format.label }}
</div>
<div class="col-auto">
{% for radio in form.display_format%}
{{ radio.tag }}
<label class="btn btn-secondary text-primary bg-white" for="{{ radio.id_for_label }}">
{{ radio.choice_label }}
</label>
{% endfor %}
</div>
</div>
<div class="row g-3 mt-4">
<div class="col-auto">
{{ form.range_start.label}}
</div>
<div class="col-auto">
<div class='col-sm-7'>
{{ form.range_start}}
</div>
</div>
</div>
<div class="row g-3 mt-4">
<div class="col-auto">
{{ form.range_end.label}}
</div>
<div class="col-auto">
<div class='col-sm-7'>
{{ form.range_end}}
</div>
</div>
</div>
<div class="form-row text-center">
<div class="col-12">
<button type="submit" class="btn btn-primary bg-white text-primary">Посмотреть статистику</button>
</div>
</div>
</form>
</div>
<ul>
{% for error in errors %}
<li><span class="badge bg-danger">{{error}}</span></li>
{% endfor %}
</ul>
<ul>
{% for warning in warnings %}
<li><span class="badge bg-warning">{{warning}}</span></li>
{% endfor %}
</ul>
<div class="container-fluid">
<table class="table table-bordered text-center text-secondary mt-5" style="background-color:#f2f2f2;">
<thead>
<tr>
<td scope="col">Пользователи/Даты</td>
{% for date in log_stats.keys %}
<td scope="col">{{date}}</td>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
<td>{{ form.email.value }}</td>
{% for time in log_stats.values %}
<td>{{time}}</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@ -285,13 +285,9 @@ class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin,SuccessMessageMi
Функция формирования контента страницы администратора (с проверкой прав доступа) Функция формирования контента страницы администратора (с проверкой прав доступа)
""" """
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
users = get_list_or_404(
UserProfile, role='agent')
context['users'] = users
context['ZENDESK_ROLES'] = ZENDESK_ROLES
context['engineers'], context['light_agents'] = count_users(get_users_list()) context['engineers'], context['light_agents'] = count_users(get_users_list())
context['licences_remaining'] = max(0, ZENDESK_MAX_AGENTS - context['engineers']) context['licences_remaining'] = max(0, ZENDESK_MAX_AGENTS - context['engineers'])
return context # TODO: need to get profile page url return context
class CustomLoginView(LoginView): class CustomLoginView(LoginView):
@ -329,8 +325,8 @@ def statistic_page(request: WSGIRequest) -> HttpResponse:
:param request: данные о пользователе: email, время и интервал работы. Данные получаем через forms.StatisticForm :param request: данные о пользователе: email, время и интервал работы. Данные получаем через forms.StatisticForm
:return: адресация на страницу статистики :return: адресация на страницу статистики
""" """
if not request.user.is_superuser: if not request.user.has_perm('main.has_control_access'):
return redirect('index') return PermissionDenied
context = { context = {
'pagename': 'страница статистики', 'pagename': 'страница статистики',
'errors': list(), 'errors': list(),
@ -352,4 +348,4 @@ def statistic_page(request: WSGIRequest) -> HttpResponse:
if request.method == 'GET': if request.method == 'GET':
form = StatisticForm() form = StatisticForm()
context['form'] = form context['form'] = form
return render(request, 'pages/stat.html', context) return render(request, 'pages/statistic.html', context)

View File

@ -4,6 +4,7 @@ Pillow==8.1.0
zenpy~=2.0.24 zenpy~=2.0.24
django_registration==3.1.1 django_registration==3.1.1
djangorestframework==3.12.2 djangorestframework==3.12.2
daphne==3.0.1
# Documentation # Documentation

View File

@ -13,8 +13,7 @@
background: #45729C; background: #45729C;
} */ } */
.form-check-input { .form-check-input {
border-radius: 0px; border-radius: 0;
background-image: url("../img/check.png");
width: 30px; width: 30px;
height: 30px; height: 30px;
background-size: 20px auto; background-size: 20px auto;
@ -125,4 +124,4 @@
padding: 10px; padding: 10px;
background: #3B91D4; background: #3B91D4;
color: white; color: white;
} }

View File

@ -1,21 +1,5 @@
"use strict"; "use strict";
function move_checkboxes() {
let checkboxes = document.getElementsByName("users");
let fields = document.querySelectorAll(".checkbox_field");
if (checkboxes.length == fields.length) {
for (let i = 0; i < fields.length; ++i) {
let el = checkboxes[i].cloneNode(true);
fields[i].appendChild(el);
}
} else {
alert(
"Количество пользователей агентов не соответствует количеству полей в форме AdminPageUsers"
);
}
}
move_checkboxes();
// React // React
class TableRow extends React.Component { class TableRow extends React.Component {
render() { render() {
@ -25,7 +9,7 @@ class TableRow extends React.Component {
<a href="#">{this.props.user.name}</a> <a href="#">{this.props.user.name}</a>
</td> </td>
<td>{this.props.user.user.email}</td> <td>{this.props.user.user.email}</td>
<td>{this.props.user.role}</td> <td>{this.props.user.zendesk_role}</td>
<td> <td>
<input <input
type="checkbox" type="checkbox"
@ -49,8 +33,8 @@ class TableBody extends React.Component {
}; };
} }
get_users() { async get_users() {
axios.get("/api/users").then((response) => { await axios.get("/api/users").then((response) => {
this.setState({ this.setState({
users: response.data.users, users: response.data.users,
engineers: response.data.engineers, engineers: response.data.engineers,
@ -62,7 +46,12 @@ class TableBody extends React.Component {
}); });
} }
delete_pretext() {
document.getElementById("loading").remove();
}
componentDidMount() { componentDidMount() {
this.get_users().then(() => this.delete_pretext());
this.interval = setInterval(() => { this.interval = setInterval(() => {
this.get_users(); this.get_users();
}, 60000); }, 60000);
@ -79,5 +68,4 @@ class TableBody extends React.Component {
} }
} }
ReactDOM.render(<TableBody />, document.getElementById("new_tbody")); ReactDOM.render(<TableBody />, document.getElementById("tbody"));
setTimeout(() => document.getElementById("old_tbody").remove(), 60000);