Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow an organization to connect to slack to receive daily sizzle time log check in alerts to a channel they choose #2973

Open
DonnieBLT opened this issue Nov 24, 2024 · 21 comments · May be fixed by #3021

Comments

@DonnieBLT
Copy link
Collaborator

DonnieBLT commented Nov 24, 2024

Certainly! Below is the code for a Django application that integrates with Slack to:
• Subscribe to daily scrum updates.
• Manage subscriptions for new bug alerts.
• Receive notifications when time is entered by workers.

This includes models, views, forms, URLs, templates, and utility functions for Slack integration. I’ve also included ways for a company to add, delete, or modify the integration.

  1. Setting Up the Django Project

First, create a new Django project and app:

django-admin startproject myproject
cd myproject
python manage.py startapp myapp

Add myapp to INSTALLED_APPS in myproject/settings.py:

INSTALLED_APPS = [
# ...
'myapp',
]

  1. Models (myapp/models.py)

a. Subscription Model

Defines user subscriptions for different notification types.

from django.contrib.auth.models import User
from django.db import models

class Subscription(models.Model):
NOTIFICATION_CHOICES = [
('scrum', 'Daily Scrum Updates'),
('bug', 'New Bug Alerts'),
('time_entry', 'Time Entry Notifications'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE)
notification_type = models.CharField(max_length=20, choices=NOTIFICATION_CHOICES)
is_subscribed = models.BooleanField(default=True)

def __str__(self):
    return f"{self.user.username} - {self.notification_type}"

b. ScrumUpdate Model

Stores daily scrum updates submitted by employees.

class ScrumUpdate(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField(auto_now_add=True)
yesterday = models.TextField()
today = models.TextField()
blockers = models.TextField(blank=True)

def __str__(self):
    return f"{self.user.username} - {self.date}"

c. Bug Model

Represents bugs reported within the system.

class Bug(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
reported_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
    return self.title

d. TimeEntry Model

Records time entries logged by employees.

class TimeEntry(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField()
hours = models.DecimalField(max_digits=5, decimal_places=2)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
    return f"{self.user.username} - {self.date} - {self.hours}h"
  1. Forms (myapp/forms.py)

Defines forms for user input.

from django import forms
from .models import ScrumUpdate, TimeEntry, Bug

class ScrumUpdateForm(forms.ModelForm):
class Meta:
model = ScrumUpdate
fields = ['yesterday', 'today', 'blockers']

class TimeEntryForm(forms.ModelForm):
class Meta:
model = TimeEntry
fields = ['date', 'hours', 'description']

class BugForm(forms.ModelForm):
class Meta:
model = Bug
fields = ['title', 'description']

  1. Views (myapp/views.py)

a. Subscription Management

Allows users to manage their notification preferences.

from django.shortcuts import render, redirect
from .models import Subscription
from django.contrib.auth.decorators import login_required

@login_required
def manage_subscriptions(request):
if request.method == 'POST':
for notification_type in ['scrum', 'bug', 'time_entry']:
is_subscribed = request.POST.get(notification_type) == 'on'
Subscription.objects.update_or_create(
user=request.user,
notification_type=notification_type,
defaults={'is_subscribed': is_subscribed}
)
return redirect('manage_subscriptions')

subscriptions = Subscription.objects.filter(user=request.user)
context = {'subscriptions': subscriptions}
return render(request, 'manage_subscriptions.html', context)

b. Submit Scrum Update

Allows employees to submit their daily scrum updates.

from .models import ScrumUpdate
from .forms import ScrumUpdateForm

@login_required
def submit_scrum_update(request):
if request.method == 'POST':
form = ScrumUpdateForm(request.POST)
if form.is_valid():
scrum_update = form.save(commit=False)
scrum_update.user = request.user
scrum_update.save()
return redirect('submit_scrum_update')
else:
form = ScrumUpdateForm()
return render(request, 'submit_scrum_update.html', {'form': form})

c. Log Time Entry

Allows employees to log their work hours.

from .models import TimeEntry
from .forms import TimeEntryForm

@login_required
def log_time_entry(request):
if request.method == 'POST':
form = TimeEntryForm(request.POST)
if form.is_valid():
time_entry = form.save(commit=False)
time_entry.user = request.user
time_entry.save()
return redirect('log_time_entry')
else:
form = TimeEntryForm()
return render(request, 'log_time_entry.html', {'form': form})

d. Report a Bug

Allows users to report bugs.

from .models import Bug
from .forms import BugForm

@login_required
def report_bug(request):
if request.method == 'POST':
form = BugForm(request.POST)
if form.is_valid():
bug = form.save(commit=False)
bug.reported_by = request.user
bug.save()
return redirect('report_bug')
else:
form = BugForm()
return render(request, 'report_bug.html', {'form': form})

  1. URLs (myapp/urls.py)

Defines URL patterns for the app.

from django.urls import path
from . import views

urlpatterns = [
path('subscriptions/', views.manage_subscriptions, name='manage_subscriptions'),
path('scrum/', views.submit_scrum_update, name='submit_scrum_update'),
path('time-entry/', views.log_time_entry, name='log_time_entry'),
path('bug-report/', views.report_bug, name='report_bug'),
]

Include the app URLs in your project’s urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('myapp.urls')),
]

  1. Templates (myapp/templates/)

a. Base Template (base.html)

<title>MyApp</title> Manage Subscriptions | Submit Scrum Update | Log Time Entry | Report Bug
{% block content %} {% endblock %}

b. Manage Subscriptions (manage_subscriptions.html)

{% extends 'base.html' %}
{% block content %}

Manage Subscriptions

{% csrf_token %} {% for choice, label in subscriptions.model.NOTIFICATION_CHOICES %} {{ label }}
{% endfor %} Save Preferences {% endblock %}

Note: You might need a custom template filter get_item to access the subscription status.

c. Submit Scrum Update (submit_scrum_update.html)

{% extends 'base.html' %}
{% block content %}

Submit Daily Scrum Update

{% csrf_token %} {{ form.as_p }} Submit {% endblock %}

d. Log Time Entry (log_time_entry.html)

{% extends 'base.html' %}
{% block content %}

Log Time Entry

{% csrf_token %} {{ form.as_p }} Log Time {% endblock %}

e. Report Bug (report_bug.html)

{% extends 'base.html' %}
{% block content %}

Report a Bug

{% csrf_token %} {{ form.as_p }} Report Bug {% endblock %}
  1. Slack Integration (myapp/utils.py)

a. Install Slack SDK

Install the Slack SDK for Python:

pip install slack-sdk

b. Utility Function to Send Messages

import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

SLACK_BOT_TOKEN = os.environ.get('SLACK_BOT_TOKEN')

client = WebClient(token=SLACK_BOT_TOKEN)

def send_slack_message(channel_id, text):
try:
response = client.chat_postMessage(
channel=channel_id,
text=text
)
except SlackApiError as e:
print(f"Error sending message: {e.response['error']}")

  1. Celery Tasks (myapp/tasks.py)

Set up Celery for asynchronous task handling.

a. Configure Celery (myproject/celery.py)

import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

b. Send Daily Scrum Updates Task

from celery import shared_task
from django.utils import timezone
from .models import ScrumUpdate, Subscription
from .utils import send_slack_message

@shared_task
def send_daily_scrum_updates():
today = timezone.now().date()
updates = ScrumUpdate.objects.filter(date=today)
if updates.exists():
message = "Daily Scrum Updates\n"
for update in updates:
message += f"{update.user.username}\n"
message += f"Yesterday: {update.yesterday}\n"
message += f"Today: {update.today}\n"
if update.blockers:
message += f"Blockers: {update.blockers}\n"
message += "\n"

    # Get users subscribed to scrum updates
    subscribers = Subscription.objects.filter(
        notification_type='scrum', is_subscribed=True
    ).values_list('user__username', flat=True)

    # Send message to a Slack channel
    channel_id = os.environ.get('SLACK_CHANNEL_ID')
    send_slack_message(channel_id, message)

c. Schedule the Task (myproject/settings.py)

from celery.schedules import crontab

CELERY_BEAT_SCHEDULE = {
'send-daily-scrum-updates': {
'task': 'myapp.tasks.send_daily_scrum_updates',
'schedule': crontab(hour=9, minute=0),
},
}

  1. Company Integration Management

a. Company Model (myapp/models.py)

If you need to manage multiple companies:

class Company(models.Model):
name = models.CharField(max_length=255)
slack_channel_id = models.CharField(max_length=50) # Company's Slack channel

def __str__(self):
    return self.name

b. Company Forms (myapp/forms.py)

class CompanyForm(forms.ModelForm):
class Meta:
model = Company
fields = ['name', 'slack_channel_id']

c. Company Views (myapp/views.py)

from .models import Company
from .forms import CompanyForm

@login_required
def add_company(request):
if request.method == 'POST':
form = CompanyForm(request.POST)
if form.is_valid():
form.save()
return redirect('company_list')
else:
form = CompanyForm()
return render(request, 'add_company.html', {'form': form})

@login_required
def delete_company(request, company_id):
company = Company.objects.get(id=company_id)
if request.method == 'POST':
company.delete()
return redirect('company_list')
return render(request, 'delete_company.html', {'company': company})

@login_required
def modify_company(request, company_id):
company = Company.objects.get(id=company_id)
if request.method == 'POST':
form = CompanyForm(request.POST, instance=company)
if form.is_valid():
form.save()
return redirect('company_list')
else:
form = CompanyForm(instance=company)
return render(request, 'modify_company.html', {'form': form})

d. Company Templates

•	Add Company (add_company.html):

{% extends 'base.html' %}
{% block content %}

Add Company

{% csrf_token %} {{ form.as_p }} Add Company {% endblock %}
•	Delete Company (delete_company.html):

{% extends 'base.html' %}
{% block content %}

Delete Company

Are you sure you want to delete {{ company.name }}?

{% csrf_token %} Delete {% endblock %}
•	Modify Company (modify_company.html):

{% extends 'base.html' %}
{% block content %}

Modify Company

{% csrf_token %} {{ form.as_p }} Save Changes {% endblock %}

e. URLs (myapp/urls.py)

urlpatterns += [
path('company/add/', views.add_company, name='add_company'),
path('company/int:company_id/delete/', views.delete_company, name='delete_company'),
path('company/int:company_id/modify/', views.modify_company, name='modify_company'),
]

  1. How Companies Can Add/Delete/Modify the Integration

    • Add Integration: Use the Add Company form to input the company name and Slack channel ID.
    • Delete Integration: Use the Delete Company view to remove the company’s integration.
    • Modify Integration: Use the Modify Company form to update company details or Slack channel ID.

  2. Running Migrations

Apply the database migrations:

python manage.py makemigrations
python manage.py migrate

  1. Starting the Server

Run the development server:

python manage.py runserver

  1. Additional Configuration

a. Environment Variables

Store sensitive information in environment variables:
• SLACK_BOT_TOKEN
• SLACK_CHANNEL_ID
• SLACK_SIGNING_SECRET (if using Slack events)

b. Setting Up Celery

Start the Celery worker and beat scheduler:

celery -A myproject worker --loglevel=info
celery -A myproject beat --loglevel=info

  1. Notes

    • Error Handling: Ensure proper error handling and validation in forms and views.
    • Authentication: Use Django’s authentication system to manage user access.
    • Permissions: Implement permissions to restrict access to company management views.
    • Security: Protect against CSRF and other security vulnerabilities.
    • Asynchronous Tasks: Use Celery for sending messages and scheduling tasks.

  2. Conclusion

This code provides a functional Django application with Slack integration for:
• Subscribing to notifications.
• Managing daily scrum updates.
• Reporting bugs.
• Logging time entries.
• Managing company integrations.

Companies can easily add, delete, or modify their integration via the provided web interface.

Feel free to ask if you need further clarification or assistance with specific parts of the code.

@krrish-sehgal
Copy link
Contributor

Could you please provide some more description on how the flow of this feature should be like ?

@apoorvapendse
Copy link
Contributor

/assign

Copy link
Contributor

Hello @apoorvapendse! You've been assigned to OWASP-BLT/BLT. You have 24 hours to complete a pull request. To place a bid and potentially earn some BCH, type /bid [amount in BCH] [BCH address].

@DonnieBLT
Copy link
Collaborator Author

This would be like an integration. If you are part of an organization, you can connect your organization to Slack and have a dedicated channel to receive messages from Blt. Those messages will be the daily stand-up items, and the user can have the option to subscribe to other alerts as well.

Copy link
Contributor

⏰ This issue has been automatically unassigned due to 24 hours of inactivity.
The issue is now available for anyone to work on again.

@krrish-sehgal
Copy link
Contributor

/assign

Copy link
Contributor

Hello @krrish-sehgal! You've been assigned to OWASP-BLT/BLT. You have 24 hours to complete a pull request. To place a bid and potentially earn some BCH, type /bid [amount in BCH] [BCH address].

@apoorvapendse
Copy link
Contributor

Hey @krrish-sehgal I am actively working on this.
#3003
I've also raised a draft PR yesterday.

@krrish-sehgal
Copy link
Contributor

krrish-sehgal commented Nov 29, 2024

@apoorvapendse seems like your draft mentioned #2851 and is maybe a subset of this issue, could you confirm what all things from this issue you've implemented.

@apoorvapendse
Copy link
Contributor

apoorvapendse commented Nov 29, 2024

yup, Its a WIP PR as mentioned in the PR message in #3003
I am done setting up the slack bot as well.
Will have to hook it up in the view.

#2851 is on the user side of things i.e to associate timelogs with an org, whereas this issue is to track the sizzle timelogs allocated by different users to the organization as per my understading

@apoorvapendse
Copy link
Contributor

I request you to kindly pick up some other issue.
Thanks

@krrish-sehgal
Copy link
Contributor

so you are saying your PR needs to be merged before i can start working on this correct ?

@apoorvapendse
Copy link
Contributor

I meant i am working on this.

@apoorvapendse
Copy link
Contributor

so you are saying your PR needs to be merged before i can start working on this correct ?

Yeah that's another dependency.

@apoorvapendse
Copy link
Contributor

The draft PR for this issue wasn't linked yesterday hence the github bot unassigned me.
Will have to investigate why that happened

@krrish-sehgal
Copy link
Contributor

Sure , all yours :)

@krrish-sehgal
Copy link
Contributor

/unassign

@apoorvapendse
Copy link
Contributor

/assign

Copy link
Contributor

Hello @apoorvapendse! You've been assigned to OWASP-BLT/BLT. You have 24 hours to complete a pull request. To place a bid and potentially earn some BCH, type /bid [amount in BCH] [BCH address].

apoorvapendse added a commit to apoorvapendse/BLT that referenced this issue Nov 30, 2024
apoorvapendse added a commit to apoorvapendse/BLT that referenced this issue Nov 30, 2024
Copy link
Contributor

⏰ This issue has been automatically unassigned due to 24 hours of inactivity.
The issue is now available for anyone to work on again.

Copy link
Contributor

⏰ This issue has been automatically unassigned due to 24 hours of inactivity.
The issue is now available for anyone to work on again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Backlog
Development

Successfully merging a pull request may close this issue.

3 participants