mirror of
https://github.com/bugsink/bugsink.git
synced 2026-05-01 04:20:17 -05:00
Hide in-progress deletions of Project & Issue from the UI
I've done a full grep on Issue.objects, Project.objects and get_object_or_404
equivelents, and applying some common sense. The goal: avoid having
confusing/half-broken pages in the UI.
On index-usage: I've decided not to update the indexes. The assumption is:
`is_deleted` items will be a tiny minority of items in general, making the
cost/benefit analysis not turn out favorably (just scanning them out as a final
step is more efficient). Also: sqlite is able to use the correct index without
adding a special one, proof:
```
EXPLAIN QUERY PLAN SELECT [..] WHERE ("issues_issue"."project_id" = 1 AND "issues_issue"."is_muted" = (0) AND "issues_issue"."is_resolved" = (0)) ORDER BY "issues_issue"."last_seen" DESC LIMIT 250;
QUERY PLAN
`--SEARCH issues_issue USING INDEX issue_list_open (project_id=? AND is_resolved=? AND is_muted=?)
EXPLAIN QUERY PLAN SELECT [..] WHERE ("issues_issue"."project_id" = 1 AND "issues_issue"."is_muted" = (0) AND "issues_issue"."is_resolved" = (0) AND "issues_issue"."is_deleted" = 0) ORDER BY "issues_issue"."last_seen" DESC LIMIT 250;
QUERY PLAN
`--SEARCH issues_issue USING INDEX issue_list_open (project_id=? AND is_resolved=? AND is_muted=?)
```
See #139 for the 0/1 notation in the above.
(Project-indexes: not an issue, the scale is "below relevance for indexes")
This commit is contained in:
@@ -39,7 +39,7 @@ def issue_membership_required(function):
|
||||
if "issue_pk" not in kwargs:
|
||||
raise TypeError("issue_pk must be passed as a keyword argument")
|
||||
issue_pk = kwargs.pop("issue_pk")
|
||||
issue = get_object_or_404(Issue, pk=issue_pk)
|
||||
issue = get_object_or_404(Issue, pk=issue_pk, is_deleted=False)
|
||||
kwargs["issue"] = issue
|
||||
if request.user.is_superuser:
|
||||
return function(request, *args, **kwargs)
|
||||
|
||||
+7
-4
@@ -131,7 +131,7 @@ class BaseIngestAPIView(View):
|
||||
@classmethod
|
||||
def get_project(cls, project_pk, sentry_key):
|
||||
try:
|
||||
return Project.objects.get(pk=project_pk, sentry_key=sentry_key)
|
||||
return Project.objects.get(pk=project_pk, sentry_key=sentry_key, is_deleted=False)
|
||||
except Project.DoesNotExist:
|
||||
# We don't distinguish between "project not found" and "key incorrect"; there's no real value in that from
|
||||
# the user perspective (they deal in dsns). Additional advantage: no need to do constant-time-comp on
|
||||
@@ -251,9 +251,12 @@ class BaseIngestAPIView(View):
|
||||
ingested_at = parse_timestamp(event_metadata["ingested_at"])
|
||||
digested_at = datetime.now(timezone.utc) if digested_at is None else digested_at # explicit passing: test only
|
||||
|
||||
project = Project.objects.get(pk=event_metadata["project_id"])
|
||||
if project.is_deleted:
|
||||
return # don't process events for deleted projects
|
||||
try:
|
||||
project = Project.objects.get(pk=event_metadata["project_id"], is_deleted=False)
|
||||
except Project.DoesNotExist:
|
||||
# we may get here if the project was deleted after the event was ingested, but before it was digested
|
||||
# (covers both "deletion in progress (is_deleted=True)" and "fully deleted").
|
||||
return
|
||||
|
||||
if not cls.count_project_periods_and_act_on_it(project, digested_at):
|
||||
return # if over-quota: just return (any cleanup is done calling-side)
|
||||
|
||||
+1
-1
@@ -308,7 +308,7 @@ def _issue_list_pt_2(request, project, state_filter, unapplied_issue_ids):
|
||||
}
|
||||
|
||||
issue_list = d_state_filter[state_filter](
|
||||
Issue.objects.filter(project=project)
|
||||
Issue.objects.filter(project=project, is_deleted=False)
|
||||
).order_by("-last_seen")
|
||||
|
||||
if request.GET.get("q"):
|
||||
|
||||
+15
-12
@@ -35,21 +35,24 @@ def project_list(request, ownership_filter=None):
|
||||
my_memberships = ProjectMembership.objects.filter(user=request.user)
|
||||
my_team_memberships = TeamMembership.objects.filter(user=request.user)
|
||||
|
||||
my_projects = Project.objects.filter(projectmembership__in=my_memberships).order_by('name').distinct()
|
||||
my_projects = Project.objects.filter(
|
||||
projectmembership__in=my_memberships, is_deleted=False).order_by('name').distinct()
|
||||
my_teams_projects = \
|
||||
Project.objects \
|
||||
.filter(team__teammembership__in=my_team_memberships) \
|
||||
.filter(team__teammembership__in=my_team_memberships, is_deleted=False) \
|
||||
.exclude(projectmembership__in=my_memberships) \
|
||||
.order_by('name').distinct()
|
||||
|
||||
if request.user.is_superuser:
|
||||
# superusers can see all project, even hidden ones
|
||||
other_projects = Project.objects \
|
||||
.filter(is_deleted=False) \
|
||||
.exclude(projectmembership__in=my_memberships) \
|
||||
.exclude(team__teammembership__in=my_team_memberships) \
|
||||
.order_by('name').distinct()
|
||||
else:
|
||||
other_projects = Project.objects \
|
||||
.filter(is_deleted=False) \
|
||||
.exclude(projectmembership__in=my_memberships) \
|
||||
.exclude(team__teammembership__in=my_team_memberships) \
|
||||
.exclude(visibility=ProjectVisibility.TEAM_MEMBERS) \
|
||||
@@ -158,7 +161,7 @@ def _check_project_admin(project, user):
|
||||
|
||||
@atomic_for_request_method
|
||||
def project_edit(request, project_pk):
|
||||
project = Project.objects.get(id=project_pk)
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
|
||||
_check_project_admin(project, request.user)
|
||||
|
||||
@@ -195,7 +198,7 @@ def project_edit(request, project_pk):
|
||||
|
||||
@atomic_for_request_method
|
||||
def project_members(request, project_pk):
|
||||
project = Project.objects.get(id=project_pk)
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
_check_project_admin(project, request.user)
|
||||
|
||||
if request.method == 'POST':
|
||||
@@ -230,7 +233,7 @@ def project_members_invite(request, project_pk):
|
||||
# NOTE: project-member invite is just that: a direct invite to a project. If you want to also/instead invite someone
|
||||
# to a team, you need to just do that instead.
|
||||
|
||||
project = Project.objects.get(id=project_pk)
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
|
||||
_check_project_admin(project, request.user)
|
||||
|
||||
@@ -292,7 +295,7 @@ def project_member_settings(request, project_pk, user_pk):
|
||||
|
||||
this_is_you = str(user_pk) == str(request.user.id)
|
||||
if not this_is_you:
|
||||
_check_project_admin(Project.objects.get(id=project_pk), request.user)
|
||||
_check_project_admin(Project.objects.get(id=project_pk, is_deleted=False), request.user)
|
||||
|
||||
membership = ProjectMembership.objects.get(project=project_pk, user=user_pk)
|
||||
create_form = lambda data: ProjectMembershipForm(data, instance=membership) # noqa
|
||||
@@ -317,7 +320,7 @@ def project_member_settings(request, project_pk, user_pk):
|
||||
return render(request, 'projects/project_member_settings.html', {
|
||||
'this_is_you': this_is_you,
|
||||
'user': User.objects.get(id=user_pk),
|
||||
'project': Project.objects.get(id=project_pk),
|
||||
'project': Project.objects.get(id=project_pk, is_deleted=False),
|
||||
'form': form,
|
||||
})
|
||||
|
||||
@@ -377,7 +380,7 @@ def project_members_accept(request, project_pk):
|
||||
# invited as user B. Security-wise this is fine, but UX-wise it could be confusing. However, I'm in the assumption
|
||||
# here that normal people (i.e. not me) don't have multiple accounts, so I'm not going to bother with this.
|
||||
|
||||
project = Project.objects.get(id=project_pk)
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
membership = ProjectMembership.objects.get(project=project, user=request.user)
|
||||
|
||||
if membership.accepted:
|
||||
@@ -402,7 +405,7 @@ def project_members_accept(request, project_pk):
|
||||
|
||||
@atomic_for_request_method
|
||||
def project_sdk_setup(request, project_pk, platform=""):
|
||||
project = Project.objects.get(id=project_pk)
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
|
||||
if not request.user.is_superuser and not ProjectMembership.objects.filter(project=project, user=request.user,
|
||||
accepted=True).exists():
|
||||
@@ -423,7 +426,7 @@ def project_sdk_setup(request, project_pk, platform=""):
|
||||
|
||||
@atomic_for_request_method
|
||||
def project_alerts_setup(request, project_pk):
|
||||
project = Project.objects.get(id=project_pk)
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
_check_project_admin(project, request.user)
|
||||
|
||||
if request.method == 'POST':
|
||||
@@ -446,7 +449,7 @@ def project_alerts_setup(request, project_pk):
|
||||
|
||||
@atomic_for_request_method
|
||||
def project_messaging_service_add(request, project_pk):
|
||||
project = Project.objects.get(id=project_pk)
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
_check_project_admin(project, request.user)
|
||||
|
||||
if request.method == 'POST':
|
||||
@@ -474,7 +477,7 @@ def project_messaging_service_add(request, project_pk):
|
||||
|
||||
@atomic_for_request_method
|
||||
def project_messaging_service_edit(request, project_pk, service_pk):
|
||||
project = Project.objects.get(id=project_pk)
|
||||
project = Project.objects.get(id=project_pk, is_deleted=False)
|
||||
_check_project_admin(project, request.user)
|
||||
|
||||
instance = project.service_configs.get(id=service_pk)
|
||||
|
||||
Reference in New Issue
Block a user