From 2e499ad104cb4f9669b149ab7c4f12e3b1859399 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:09:59 +0000 Subject: [PATCH 1/5] chore(deps-dev): Bump ruff from 0.9.1 to 0.9.2 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.1 to 0.9.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.9.1...0.9.2) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 40 ++++++++++++++++++++-------------------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index a70e9f441..87a16f4ca 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4005,29 +4005,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.9.1" +version = "0.9.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.9.1-py3-none-linux_armv6l.whl", hash = "sha256:84330dda7abcc270e6055551aca93fdde1b0685fc4fd358f26410f9349cf1743"}, - {file = "ruff-0.9.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3cae39ba5d137054b0e5b472aee3b78a7c884e61591b100aeb544bcd1fc38d4f"}, - {file = "ruff-0.9.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:50c647ff96f4ba288db0ad87048257753733763b409b2faf2ea78b45c8bb7fcb"}, - {file = "ruff-0.9.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0c8b149e9c7353cace7d698e1656ffcf1e36e50f8ea3b5d5f7f87ff9986a7ca"}, - {file = "ruff-0.9.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:beb3298604540c884d8b282fe7625651378e1986c25df51dec5b2f60cafc31ce"}, - {file = "ruff-0.9.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39d0174ccc45c439093971cc06ed3ac4dc545f5e8bdacf9f067adf879544d969"}, - {file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69572926c0f0c9912288915214ca9b2809525ea263603370b9e00bed2ba56dbd"}, - {file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:937267afce0c9170d6d29f01fcd1f4378172dec6760a9f4dface48cdabf9610a"}, - {file = "ruff-0.9.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:186c2313de946f2c22bdf5954b8dd083e124bcfb685732cfb0beae0c47233d9b"}, - {file = "ruff-0.9.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f94942a3bb767675d9a051867c036655fe9f6c8a491539156a6f7e6b5f31831"}, - {file = "ruff-0.9.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:728d791b769cc28c05f12c280f99e8896932e9833fef1dd8756a6af2261fd1ab"}, - {file = "ruff-0.9.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2f312c86fb40c5c02b44a29a750ee3b21002bd813b5233facdaf63a51d9a85e1"}, - {file = "ruff-0.9.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ae017c3a29bee341ba584f3823f805abbe5fe9cd97f87ed07ecbf533c4c88366"}, - {file = "ruff-0.9.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5dc40a378a0e21b4cfe2b8a0f1812a6572fc7b230ef12cd9fac9161aa91d807f"}, - {file = "ruff-0.9.1-py3-none-win32.whl", hash = "sha256:46ebf5cc106cf7e7378ca3c28ce4293b61b449cd121b98699be727d40b79ba72"}, - {file = "ruff-0.9.1-py3-none-win_amd64.whl", hash = "sha256:342a824b46ddbcdddd3abfbb332fa7fcaac5488bf18073e841236aadf4ad5c19"}, - {file = "ruff-0.9.1-py3-none-win_arm64.whl", hash = "sha256:1cd76c7f9c679e6e8f2af8f778367dca82b95009bc7b1a85a47f1521ae524fa7"}, - {file = "ruff-0.9.1.tar.gz", hash = "sha256:fd2b25ecaf907d6458fa842675382c8597b3c746a2dde6717fe3415425df0c17"}, + {file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"}, + {file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"}, + {file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"}, + {file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"}, + {file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"}, + {file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"}, + {file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"}, + {file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"}, + {file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"}, ] [[package]] @@ -5020,4 +5020,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "3.11.2" -content-hash = "412a4fd338ee82b381e9415f5182696fbf165b11d693fcf80e5e6e428b783a8f" +content-hash = "780e52e5dabecef51e218b0095e719e55efd253cd2da92ce4c64c9412a66c18e" diff --git a/pyproject.toml b/pyproject.toml index 4b88bfc9f..085a622ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ newrelic = "^10.4.0" [tool.poetry.group.dev.dependencies] black = "^24.8.0" isort = "^5.13.2" -ruff = "^0.9.1" +ruff = "^0.9.2" pre-commit = "^3.8.0" [tool.isort] From e48b4ae6ce17d504179493d64abc108066d37d9e Mon Sep 17 00:00:00 2001 From: Sahil Omkumar Dhillon <118592065+SahilDhillon21@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:11:43 +0530 Subject: [PATCH 2/5] Add schedule management commands (#3237) --- website/management/commands/run_daily.py | 23 ++++++++++++++++++++++ website/management/commands/run_hourly.py | 23 ++++++++++++++++++++++ website/management/commands/run_monthly.py | 23 ++++++++++++++++++++++ website/management/commands/run_weekly.py | 23 ++++++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 website/management/commands/run_daily.py create mode 100644 website/management/commands/run_hourly.py create mode 100644 website/management/commands/run_monthly.py create mode 100644 website/management/commands/run_weekly.py diff --git a/website/management/commands/run_daily.py b/website/management/commands/run_daily.py new file mode 100644 index 000000000..fa3859797 --- /dev/null +++ b/website/management/commands/run_daily.py @@ -0,0 +1,23 @@ +import logging + +from django.core.management.base import BaseCommand + +# from django.core import management +from django.utils import timezone + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "Runs commands scheduled to execute daily" + + def handle(self, *args, **options): + try: + logger.info(f"Starting daily scheduled tasks at {timezone.now()}") + + # Add commands to be executed daily + # management.call_command('daily_command1') + # management.call_command('daily_command2') + except Exception as e: + logger.error(f"Error in daily tasks: {str(e)}") + raise diff --git a/website/management/commands/run_hourly.py b/website/management/commands/run_hourly.py new file mode 100644 index 000000000..137b49cde --- /dev/null +++ b/website/management/commands/run_hourly.py @@ -0,0 +1,23 @@ +import logging + +from django.core.management.base import BaseCommand + +# from django.core import management +from django.utils import timezone + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "Runs commands scheduled to execute hourly" + + def handle(self, *args, **options): + try: + logger.info(f"Starting hourly scheduled tasks at {timezone.now()}") + + # We can add hourly commands here + # management.call_command('hourly_command1') + # management.call_command('hourly_command2') + except Exception as e: + logger.error(f"Error in hourly tasks: {str(e)}") + raise diff --git a/website/management/commands/run_monthly.py b/website/management/commands/run_monthly.py new file mode 100644 index 000000000..92adf6f73 --- /dev/null +++ b/website/management/commands/run_monthly.py @@ -0,0 +1,23 @@ +import logging + +from django.core.management.base import BaseCommand + +# from django.core import management +from django.utils import timezone + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "Runs commands scheduled to execute monthly" + + def handle(self, *args, **options): + try: + logger.info(f"Starting monthly scheduled tasks at {timezone.now()}") + + # Add commands to be executed monthly + # management.call_command('monthly_command1') + # management.call_command('monthly_command2') + except Exception as e: + logger.error(f"Error in monthly tasks: {str(e)}") + raise diff --git a/website/management/commands/run_weekly.py b/website/management/commands/run_weekly.py new file mode 100644 index 000000000..f0221cddb --- /dev/null +++ b/website/management/commands/run_weekly.py @@ -0,0 +1,23 @@ +import logging + +from django.core.management.base import BaseCommand + +# from django.core import management +from django.utils import timezone + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "Runs commands scheduled to execute weekly" + + def handle(self, *args, **options): + try: + logger.info(f"Starting weekly scheduled tasks at {timezone.now()}") + + # Add commands to be executed weekly + # management.call_command('weekly_command1') + # management.call_command('weekly_command2') + except Exception as e: + logger.error(f"Error in weekly tasks: {str(e)}") + raise From 5731a355fd774526abf9d2f0fc86b9649ba5267f Mon Sep 17 00:00:00 2001 From: Altafur Rahman Date: Sat, 18 Jan 2025 15:49:55 +0600 Subject: [PATCH 3/5] feat(slack): add Slack event handling for team joins and messages (#3239) --- blt/urls.py | 2 + website/views/slack_handlers.py | 192 ++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 website/views/slack_handlers.py diff --git a/blt/urls.py b/blt/urls.py index a2ec22799..fbe739c1b 100644 --- a/blt/urls.py +++ b/blt/urls.py @@ -185,6 +185,7 @@ distribute_bacon, select_contribution, ) +from website.views.slack_handlers import slack_events from website.views.teams import ( TeamOverview, add_member, @@ -851,6 +852,7 @@ ), path("projects/create/", create_project, name="create_project"), path("project//", ProjectsDetailView.as_view(), name="projects_detail"), + path("slack/events", slack_events, name="slack_events"), ] if settings.DEBUG: diff --git a/website/views/slack_handlers.py b/website/views/slack_handlers.py new file mode 100644 index 000000000..70f125e59 --- /dev/null +++ b/website/views/slack_handlers.py @@ -0,0 +1,192 @@ +import hashlib +import hmac +import json +import os +import time + +from django.http import HttpResponse, JsonResponse +from django.views.decorators.csrf import csrf_exempt +from dotenv import load_dotenv +from slack import WebClient +from slack_sdk.errors import SlackApiError + +load_dotenv() + +DEPLOYS_CHANNEL_NAME = "#project-blt-lettuce-deploys" +JOINS_CHANNEL_ID = "C076DAG65AT" +CONTRIBUTE_ID = "C077QBBLY1Z" + +SLACK_TOKEN = os.getenv("SLACK_TOKEN") +SIGNING_SECRET = os.getenv("SIGNING_SECRET") +client = WebClient(token=SLACK_TOKEN) + + +def verify_slack_signature(request): + timestamp = request.headers.get("X-Slack-Request-Timestamp", "") + signature = request.headers.get("X-Slack-Signature", "") + + # Verify timestamp to prevent replay attacks + if abs(time.time() - float(timestamp)) > 60 * 5: + return False + + sig_basestring = f"v0:{timestamp}:{request.body.decode()}" + my_signature = ( + "v0=" + + hmac.new(SIGNING_SECRET.encode(), sig_basestring.encode(), hashlib.sha256).hexdigest() + ) + + return hmac.compare_digest(my_signature, signature) + + +@csrf_exempt +def slack_events(request): + """Handle incoming Slack events""" + if request.method == "POST": + # Verify the request is from Slack + if not verify_slack_signature(request): + return HttpResponse(status=403) + + data = json.loads(request.body) + + if "challenge" in data: + return JsonResponse({"challenge": data["challenge"]}) + + event = data.get("event", {}) + event_type = event.get("type") + + if event_type == "team_join": + user_data = event.get("user", {}) + if isinstance(user_data, dict): + user_id = user_data.get("id") + else: + user_id = event.get("user") + + if user_id: + _handle_team_join(user_id) + + elif event_type == "message": + handle_message(event) + + return HttpResponse(status=200) + return HttpResponse(status=405) + + +def extract_text_from_blocks(blocks): + """Extracts message text from Slack's 'blocks' format""" + if not blocks: + return "" + + text_parts = [] + for block in blocks: + if block.get("type") == "rich_text": + for element in block.get("elements", []): + if element.get("type") == "rich_text_section": + for item in element.get("elements", []): + if item.get("type") == "text": + text_parts.append(item.get("text", "")) + + return " ".join(text_parts).strip() + + +def _handle_contribute_message(message): + text = message.get("text", "").lower() + user = message.get("user") + channel = message.get("channel") + + if message.get("subtype") is None and any( + keyword in text for keyword in ["contribute", "contributing", "contributes"] + ): + response = client.chat_postMessage( + channel=channel, + text=f"Hello <@{user}>! Please check <#{CONTRIBUTE_ID}> for contributing guidelines today!", + ) + + +def _handle_team_join(user_id): + # Send message to joins channel + join_response = client.chat_postMessage( + channel=JOINS_CHANNEL_ID, text=f"Welcome <@{user_id}> to the team! 🎉" + ) + + try: + # Try to open DM first + dm_response = client.conversations_open(users=[user_id]) + if not dm_response["ok"]: + return + + dm_channel = dm_response["channel"]["id"] + + # Define welcome message + welcome_message = ( + f":tada: *Welcome to the OWASP Slack Community, <@{user_id}>!* :tada:\n\n" + "We're thrilled to have you here! Whether you're new to OWASP or a long-time contributor, " + "this Slack workspace is the perfect place to connect, collaborate, and stay informed about all things OWASP.\n\n" + ":small_blue_diamond: *Get Involved:*\n" + "• Check out the *#contribute* channel to find ways to get involved with OWASP projects and initiatives.\n" + "• Explore individual project channels, which are named *#project-name*, to dive into specific projects that interest you.\n" + "• Join our chapter channels, named *#chapter-name*, to connect with local OWASP members in your area.\n\n" + ":small_blue_diamond: *Stay Updated:*\n" + "• Visit *#newsroom* for the latest updates and announcements.\n" + "• Follow *#external-activities* for news about OWASP's engagement with the wider security community.\n\n" + ":small_blue_diamond: *Connect and Learn:*\n" + "• *#jobs*: Looking for new opportunities? Check out the latest job postings here.\n" + "• *#leaders*: Connect with OWASP leaders and stay informed about leadership activities.\n" + "• *#project-committee*: Engage with the committee overseeing OWASP projects.\n" + "• *#gsoc*: Stay updated on Google Summer of Code initiatives.\n" + "• *#github-admins*: Get support and discuss issues related to OWASP's GitHub repositories.\n" + "• *#learning*: Share and find resources to expand your knowledge in the field of application security.\n\n" + "We're excited to see the amazing contributions you'll make. If you have any questions or need assistance, don't hesitate to ask. " + "Let's work together to make software security visible and improve the security of the software we all rely on.\n\n" + "Welcome aboard! :rocket:" + ) + + welcome_blocks = [{"type": "section", "text": {"type": "mrkdwn", "text": welcome_message}}] + + welcome_response = client.chat_postMessage( + channel=dm_channel, text=welcome_message, blocks=welcome_blocks + ) + + except SlackApiError as e: + return HttpResponse(status=500) + + +def handle_message(payload): + # Get bot user ID + response = client.auth_test() + bot_user_id = response["user_id"] + + # Skip if message is from the bot + if payload.get("user") == bot_user_id: + return + + # Get message content from both text and blocks + text = payload.get("text", "") + blocks_text = extract_text_from_blocks(payload.get("blocks", [])) + + # Use text from blocks if direct text is empty + message_text = text or blocks_text + + # Create message object with the extracted text + message = { + "user": payload.get("user"), + "channel": payload.get("channel"), + "text": message_text, + "subtype": payload.get("subtype"), + "channel_type": payload.get("channel_type"), + } + + _handle_contribute_message(message) + _handle_direct_message(message, bot_user_id) + + +def _handle_direct_message(message, bot_user_id): + if message.get("channel_type") == "im": + user = message["user"] + text = message.get("text", "") + + try: + if message.get("user") != bot_user_id: + client.chat_postMessage(channel=JOINS_CHANNEL_ID, text=f"<@{user}> said {text}") + client.chat_postMessage(channel=user, text=f"Hello <@{user}>, you said: {text}") + except SlackApiError as e: + return HttpResponse(status=500) From b59a90951d4a74df05e10ad279a24de5a3278b1e Mon Sep 17 00:00:00 2001 From: Altafur Rahman Date: Sun, 19 Jan 2025 06:47:23 +0600 Subject: [PATCH 4/5] test(slack): add unit tests for Slack message handling functions (#3240) --- website/test_slack.py | 89 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 website/test_slack.py diff --git a/website/test_slack.py b/website/test_slack.py new file mode 100644 index 000000000..5b4dfe8c1 --- /dev/null +++ b/website/test_slack.py @@ -0,0 +1,89 @@ +from unittest.mock import MagicMock, patch + +from django.test import TestCase + +from website.views.slack_handlers import ( + _handle_contribute_message, + _handle_team_join, + extract_text_from_blocks, + handle_message, +) + + +class SlackFunctionTests(TestCase): + def setUp(self): + self.mock_client = MagicMock() + + def test_extract_text_from_blocks(self): + """Test extracting text from Slack block format""" + # Test rich text blocks + blocks = [ + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "I want to contribute"}], + } + ], + } + ] + + self.assertEqual(extract_text_from_blocks(blocks), "I want to contribute") + + # Test empty blocks + self.assertEqual(extract_text_from_blocks([]), "") + + # Test invalid blocks + self.assertEqual(extract_text_from_blocks(None), "") + + @patch("website.views.slack_handlers.client") + def test_handle_contribute_message(self, mock_client): + """Test contribute message handler""" + message = { + "user": "U123", + "channel": "C123", + "text": "How do I contribute?", + "subtype": None, + } + + _handle_contribute_message(message) + + mock_client.chat_postMessage.assert_called_once() + + # Test message without contribute keyword + message["text"] = "Hello world" + mock_client.chat_postMessage.reset_mock() + _handle_contribute_message(message) + mock_client.chat_postMessage.assert_not_called() + + @patch("website.views.slack_handlers.client") + def test_handle_team_join(self, mock_client): + """Test team join handler""" + mock_client.conversations_open.return_value = {"ok": True, "channel": {"id": "D123"}} + + _handle_team_join("U123") + + # Should send welcome message in joins channel + mock_client.chat_postMessage.assert_called() + + # Should try to open DM + mock_client.conversations_open.assert_called_once_with(users=["U123"]) + + @patch("website.views.slack_handlers.client") + def test_handle_message(self, mock_client): + """Test main message handler""" + # Mock bot user ID + mock_client.auth_test.return_value = {"user_id": "BOT123"} + + # Test normal user message + payload = {"user": "U123", "text": "contribute", "channel": "C123"} + + handle_message(payload) + mock_client.chat_postMessage.assert_called() + + # Test bot message (should be ignored) + payload["user"] = "BOT123" + mock_client.chat_postMessage.reset_mock() + handle_message(payload) + mock_client.chat_postMessage.assert_not_called() From 01b62f0756ee5a2d3f688016574e3c5df55f5807 Mon Sep 17 00:00:00 2001 From: Sahil Omkumar Dhillon <118592065+SahilDhillon21@users.noreply.github.com> Date: Sun, 19 Jan 2025 06:26:02 +0530 Subject: [PATCH 5/5] team overview dashboard (#3233) * team overview dashboard * add img h w --- blt/urls.py | 6 + .../organization_includes/sidebar.html | 8 +- .../organization_team_overview.html | 473 ++++++++++++++++++ website/views/company.py | 80 +++ 4 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 website/templates/organization/organization_team_overview.html diff --git a/blt/urls.py b/blt/urls.py index fbe739c1b..adfa7f302 100644 --- a/blt/urls.py +++ b/blt/urls.py @@ -55,6 +55,7 @@ OrganizationDashboardManageBugsView, OrganizationDashboardManageDomainsView, OrganizationDashboardManageRolesView, + OrganizationDashboardTeamOverviewView, RegisterOrganizationView, ShowBughuntView, SlackCallbackView, @@ -693,6 +694,11 @@ OrganizationDashboardManageBugsView.as_view(), name="organization_manage_bugs", ), + path( + "organization//dashboard/team-overview/", + OrganizationDashboardTeamOverviewView.as_view(), + name="organization_team_overview", + ), path( "organization//dashboard/domains/", OrganizationDashboardManageDomainsView.as_view(), diff --git a/website/templates/organization/organization_includes/sidebar.html b/website/templates/organization/organization_includes/sidebar.html index b4e1211ee..9be221720 100644 --- a/website/templates/organization/organization_includes/sidebar.html +++ b/website/templates/organization/organization_includes/sidebar.html @@ -18,6 +18,12 @@