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

Remove target input from form if its fixed, add ability to share targ… #1142

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/managing_data/continuous_sharing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ sharing after you have bulk created the `ReducedDatums`:

.. code:: python

from tom_dataproducts.sharing import continuous_share_data
from tom_targets.sharing import continuous_share_data
# After all your logic to bulk_create ReducedDatums
# Trigger any sharing you may have set to occur when new data comes in
# Encapsulate this in a try/catch so sharing failure doesn't prevent dataproduct ingestion
Expand Down
2 changes: 1 addition & 1 deletion tom_dataproducts/data_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from importlib import import_module

from tom_dataproducts.models import ReducedDatum
from tom_dataproducts.sharing import continuous_share_data
from tom_targets.sharing import continuous_share_data

logger = logging.getLogger(__name__)

Expand Down
61 changes: 23 additions & 38 deletions tom_dataproducts/sharing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,13 @@
from django.http import StreamingHttpResponse
from django.utils.text import slugify

from tom_targets.models import Target, PersistentShare
from tom_targets.models import Target

from tom_dataproducts.models import DataProduct, ReducedDatum
from tom_dataproducts.alertstreams.hermes import publish_to_hermes, BuildHermesMessage, get_hermes_topics
from tom_dataproducts.serializers import DataProductSerializer, ReducedDatumSerializer


def continuous_share_data(target, reduced_datums):
"""
Triggered when new ReducedDatums are created.
Shares those ReducedDatums to the sharing destination of any PersistentShares on the target.
:param target: Target instance that these reduced_datums belong to
:param reduced_datums: list of ReducedDatum instances to share
"""
persistentshares = PersistentShare.objects.filter(target=target)
for persistentshare in persistentshares:
share_destination = persistentshare.destination
reduced_datum_pks = [rd.pk for rd in reduced_datums]
if 'HERMES' in share_destination.upper():
hermes_topic = share_destination.split(':')[1]
destination = share_destination.split(':')[0]
filtered_reduced_datums = check_for_share_safe_datums(
destination, ReducedDatum.objects.filter(pk__in=reduced_datum_pks), topic=hermes_topic)
sharing = getattr(settings, "DATA_SHARING", {})
message = BuildHermesMessage(title=f"Updated data for {target.name} from "
f"{getattr(settings, 'TOM_NAME', 'TOM Toolkit')}.",
authors=sharing.get('hermes', {}).get('DEFAULT_AUTHORS', None),
message=None,
topic=hermes_topic
)
publish_to_hermes(message, filtered_reduced_datums)
else:
share_data_with_tom(share_destination, None, None, None, selected_data=reduced_datum_pks)


def share_target_list_with_hermes(share_destination, form_data, selected_targets=None, include_all_data=False):
"""
Serialize and share a set of selected targets and their data with Hermes
Expand Down Expand Up @@ -273,13 +246,16 @@ def check_for_save_safe_datums():
return


def get_sharing_destination_options():
def get_sharing_destination_options(include_download=True):
"""
Build the Display options and headers for the dropdown form for choosing sharing topics.
Customize for a different selection experience.
:return: Tuple: Possible Destinations and their Display Names
"""
choices = [('download', 'download')]
if include_download:
choices = [('download', 'download')]
else:
choices = []
try:
for destination, details in settings.DATA_SHARING.items():
new_destination = [details.get('DISPLAY_NAME', destination)]
Expand All @@ -306,21 +282,30 @@ def get_sharing_destination_options():
return tuple(choices)


def sharing_feedback_handler(response, request):
def sharing_feedback_converter(response):
"""
Handle the response from a sharing request and prepare a message to the user
:return:
Takes a sharing feedback response and returns its error or success message
"""
try:
response.raise_for_status()
if 'message' in response.json():
publish_feedback = response.json()['message']
feedback_message = response.json()['message']
else:
publish_feedback = "Submitted message succesfully"
feedback_message = "Submitted message succesfully"
except AttributeError:
publish_feedback = response['message']
feedback_message = response['message']
except Exception:
publish_feedback = f"ERROR: Returned Response code {response.status_code} with content: {response.content}"
feedback_message = f"ERROR: Returned Response code {response.status_code} with content: {response.content}"

return feedback_message


def sharing_feedback_handler(response, request):
"""
Handle the response from a sharing request and prepare a message to the user
:return:
"""
publish_feedback = sharing_feedback_converter(response)
if "ERROR" in publish_feedback.upper():
messages.error(request, publish_feedback)
else:
Expand Down
3 changes: 2 additions & 1 deletion tom_targets/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,12 @@ class Meta:

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['destination'].choices = get_sharing_destination_options()
self.fields['destination'].choices = get_sharing_destination_options(include_download=False)


class PersistentShareForm(AdminPersistentShareForm):
target = forms.IntegerField(label='Target ID', initial=0, required=True)
share_existing_data = forms.BooleanField(label='Share existing data immediately', required=False, initial=False)

def __init__(self, *args, **kwargs):
try:
Expand Down
28 changes: 28 additions & 0 deletions tom_targets/persistent_sharing_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from rest_framework import serializers
from tom_dataproducts.sharing import get_sharing_destination_options
from tom_targets.sharing import share_target_and_all_data
from tom_targets.fields import TargetFilteredPrimaryKeyRelatedField
from tom_targets.models import PersistentShare, Target


class PersistentShareSerializer(serializers.ModelSerializer):
destination = serializers.ChoiceField(
choices=get_sharing_destination_options(include_download=False), required=True
)
target = TargetFilteredPrimaryKeyRelatedField(queryset=Target.objects.all(), required=True)
share_existing_data = serializers.BooleanField(default=False, required=False, write_only=True)

class Meta:
model = PersistentShare
fields = ('id', 'target', 'destination', 'user', 'created', 'share_existing_data')

def create(self, validated_data):
shared_existing_data = validated_data.pop('share_existing_data', None)
if shared_existing_data:
sharing_feedback = share_target_and_all_data(validated_data['destination'], validated_data['target'])
if 'ERROR' in sharing_feedback.upper():
raise serializers.ValidationError(
f"Failed to share existing data of target {validated_data['target'].name}: {sharing_feedback}"

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix AI 12 days ago

To fix the problem, we need to modify the error message to be more generic and avoid exposing sensitive information. Instead of including the target's name and the detailed sharing feedback in the error message, we should log the detailed error message on the server and return a generic error message to the user.

  1. Modify the create method in PersistentShareSerializer to log the detailed error message and raise a generic ValidationError.
  2. Add the necessary import for logging.
Suggested changeset 1
tom_targets/persistent_sharing_serializers.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tom_targets/persistent_sharing_serializers.py b/tom_targets/persistent_sharing_serializers.py
--- a/tom_targets/persistent_sharing_serializers.py
+++ b/tom_targets/persistent_sharing_serializers.py
@@ -5,3 +5,5 @@
 from tom_targets.models import PersistentShare, Target
+import logging
 
+logger = logging.getLogger(__name__)
 
@@ -23,5 +25,4 @@
             if 'ERROR' in sharing_feedback.upper():
-                raise serializers.ValidationError(
-                    f"Failed to share existing data of target {validated_data['target'].name}: {sharing_feedback}"
-                )
+                logger.error(f"Failed to share existing data of target {validated_data['target'].name}: {sharing_feedback}")
+                raise serializers.ValidationError("Failed to share existing data of the target.")
 
EOF
@@ -5,3 +5,5 @@
from tom_targets.models import PersistentShare, Target
import logging

logger = logging.getLogger(__name__)

@@ -23,5 +25,4 @@
if 'ERROR' in sharing_feedback.upper():
raise serializers.ValidationError(
f"Failed to share existing data of target {validated_data['target'].name}: {sharing_feedback}"
)
logger.error(f"Failed to share existing data of target {validated_data['target'].name}: {sharing_feedback}")
raise serializers.ValidationError("Failed to share existing data of the target.")

Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
)

return super().create(validated_data)
13 changes: 1 addition & 12 deletions tom_targets/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
from rest_framework import serializers

from tom_common.serializers import GroupSerializer
from tom_targets.models import Target, TargetExtra, TargetName, TargetList, PersistentShare
from tom_targets.models import Target, TargetExtra, TargetName, TargetList
from tom_targets.validators import RequiredFieldsTogetherValidator
from tom_targets.fields import TargetFilteredPrimaryKeyRelatedField
from tom_dataproducts.sharing import get_sharing_destination_options


class TargetNameSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -181,12 +179,3 @@ def update(self, instance, validated_data):
instance.save()

return instance


class PersistentShareSerializer(serializers.ModelSerializer):
destination = serializers.ChoiceField(choices=get_sharing_destination_options(), required=True)
target = TargetFilteredPrimaryKeyRelatedField(queryset=Target.objects.all(), required=True)

class Meta:
model = PersistentShare
fields = ('id', 'target', 'destination', 'user', 'created')
66 changes: 65 additions & 1 deletion tom_targets/sharing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,71 @@
from django.core.exceptions import ImproperlyConfigured

from tom_targets.serializers import TargetSerializer
from tom_dataproducts.sharing import get_destination_target
from tom_targets.models import PersistentShare
from tom_dataproducts.sharing import (check_for_share_safe_datums, share_data_with_tom,
get_destination_target, sharing_feedback_converter)
from tom_dataproducts.models import ReducedDatum
from tom_dataproducts.alertstreams.hermes import publish_to_hermes, BuildHermesMessage


def share_target_and_all_data(share_destination, target):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is silently breaking for me.
I'm getting back b'{"message_text":["This field may not be null."],"submitter":["This field may not be blank."]}'
Obviously these things need to be autofilled or use the other form.
It would also be nice if these made the same user facing alerts that normal sharing does (for success as well as failure.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking that - I had thought submitter would be autoset by Hermes but it actually doesn't do that for outgoing messages, only incoming. So I've set the submitter to be the TOM_NAME which seems appropriate to me, and set the message text to be empty string instead of None.

Copy link
Contributor Author

@jnation3406 jnation3406 Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the error should be mapped through now so it should stop the creation of a persistent share and show the error on screen if you have a problem when sharing all existing data. If it succeeds, nothing is shown but the persistent share is created and will show up, so that is the signal that it succeeded in sharing existing data too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still getting a silent failure for HTTPError('400 Client Error: Bad Request for url: https://hermes-dev.lco.global/api/v0/submit_message/') b'{"topic":["Hermes Dev can only submit to the hermes.test topic."]}'

as well as missing telescope/instrument

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I'll look into the error not making it through... What do you mean by missing telescope/instrument though? Is it stored in your datums value dict as 'telescope' and 'instrument'?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, not having the telescope/instrument in my data is just another way my hermes message can be rejected. The issue isn't that, it's just one of the two ways I tried to get errors back from hermes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay I understand what were saying now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated it again, and the errors are definitely coming through now! Sorry the error feedback handler stuff had me confused a bit.

"""
Given a sharing destination, shares the target and all its current dataproducts
with that destination. Will raise an Exception is any portion of sharing fails.
:param share_destination: String sharing destination from the DATA_SHARING setting
:param target: Target instance that should be shared with all its data
"""
if 'HERMES' in share_destination.upper():
hermes_topic = share_destination.split(':')[1]
destination = share_destination.split(':')[0]
filtered_reduced_datums = check_for_share_safe_datums(
destination, ReducedDatum.objects.filter(target=target), topic=hermes_topic)
sharing = getattr(settings, "DATA_SHARING", {})
tom_name = f"{getattr(settings, 'TOM_NAME', 'TOM Toolkit')}"
message = BuildHermesMessage(title=f"Setting up continuous sharing for {target.name} from "
f"{tom_name}.",
authors=sharing.get('hermes', {}).get('DEFAULT_AUTHORS', None),
submitter='',
message='',
topic=hermes_topic
)
return sharing_feedback_converter(publish_to_hermes(message, filtered_reduced_datums))
else:
response = share_target_with_tom(share_destination, {'target': target})
response_feedback = sharing_feedback_converter(response)
if 'ERROR' in response_feedback.upper():
return response_feedback
return sharing_feedback_converter(share_data_with_tom(share_destination, None, target_id=target.id))


def continuous_share_data(target, reduced_datums):
"""
Triggered when new ReducedDatums are created.
Shares those ReducedDatums to the sharing destination of any PersistentShares on the target.
:param target: Target instance that these reduced_datums belong to
:param reduced_datums: list of ReducedDatum instances to share
"""
persistentshares = PersistentShare.objects.filter(target=target)
for persistentshare in persistentshares:
share_destination = persistentshare.destination
reduced_datum_pks = [rd.pk for rd in reduced_datums]
if 'HERMES' in share_destination.upper():
hermes_topic = share_destination.split(':')[1]
destination = share_destination.split(':')[0]
filtered_reduced_datums = check_for_share_safe_datums(
destination, ReducedDatum.objects.filter(pk__in=reduced_datum_pks), topic=hermes_topic)
sharing = getattr(settings, "DATA_SHARING", {})
tom_name = f"{getattr(settings, 'TOM_NAME', 'TOM Toolkit')}"
message = BuildHermesMessage(title=f"Updated data for {target.name} from "
f"{tom_name}.",
authors=sharing.get('hermes', {}).get('DEFAULT_AUTHORS', None),
submitter=tom_name,
message='',
topic=hermes_topic
)
publish_to_hermes(message, filtered_reduced_datums)
else:
share_data_with_tom(share_destination, None, None, None, selected_data=reduced_datum_pks)


def share_target_with_tom(share_destination, form_data, target_lists=()):
Expand Down
2 changes: 1 addition & 1 deletion tom_targets/signals/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.db.models.signals import post_save

from tom_dataproducts.models import ReducedDatum
from tom_dataproducts.sharing import continuous_share_data
from tom_targets.sharing import continuous_share_data


@receiver(post_save, sender=ReducedDatum)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
<form method="post" class="form" id='target-persistent-share-create-form'>
{% csrf_token %}
<div class="form-row" style="padding-inline:1rem">
<div class="col-sm-3">
<div class="col-sm-4">
{% bootstrap_field form.destination %}
</div>
<div class="col-sm-5">
{% if not target %}
jnation3406 marked this conversation as resolved.
Show resolved Hide resolved
<div class="col-sm-2">
{% bootstrap_field form.target %}
</div>
{% endif %}
<div class="col-sm-4 offset-sm-1" style="align-content:end;">
{% bootstrap_field form.share_existing_data %}
</div>
<div class="col-sm-1">
{% if target %}
<input type="button" class="btn btn-primary" value="Create" onclick="createPersistentShare('{% url 'targets:persistent-share' %}', '{% url 'targets:target-persistent-share-manage-table' target.pk %}')" style="position:absolute; bottom:1rem"/>
Expand Down Expand Up @@ -43,9 +48,11 @@
async function createPersistentShare(createUrl, updateUrl) {
var target_id = document.getElementById('id_target').value;
var destination = document.getElementById('id_destination').value;
var share_existing = document.getElementById('id_share_existing_data').checked;
var payload = {
"destination": destination,
"target": target_id
"target": target_id,
"share_existing_data": share_existing
}
const response = await fetch(createUrl, {
method: 'POST',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{% load targets_extras %}
<table class="table table-hover">
<thead>
<tr>
Expand All @@ -15,7 +16,7 @@
<td>
<a href="{% url 'targets:detail' persistentshare.target.id %}" title="{{ persistentshare.target.name }}">{{ persistentshare.target.names|join:", " }}</a>
</td>
<td>{{ persistentshare.destination }}</td>
<td>{{ persistentshare.destination|bold_sharing_source }}</td>
<td>{{ persistentshare.user.username }}</td>
{% if can_delete %}
<td>
Expand Down
9 changes: 9 additions & 0 deletions tom_targets/templatetags/targets_extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from astropy.coordinates import Angle, get_body, SkyCoord
from astropy.time import Time
from django import template
from django.utils.safestring import mark_safe
from django.conf import settings
from django.db.models import Q
from django.apps import apps
Expand All @@ -24,6 +25,14 @@
logger.setLevel(logging.DEBUG)


@register.filter(name='bold_sharing_source')
def bold_sharing_source(value):
pieces = value.split(':')
if len(pieces) > 1:
return mark_safe(f"<strong>{pieces[0]}</strong>:{':'.join(pieces[1:])}")
return value


@register.inclusion_tag('tom_targets/partials/recent_targets.html', takes_context=True)
def recent_targets(context, limit=10):
"""
Expand Down
2 changes: 1 addition & 1 deletion tom_targets/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
)
from tom_targets.merge import (merge_error_message)
from tom_targets.models import Target, TargetList
from tom_targets.serializers import PersistentShareSerializer
from tom_targets.persistent_sharing_serializers import PersistentShareSerializer
from tom_targets.templatetags.targets_extras import target_merge_fields, persistent_share_table
from tom_targets.utils import import_targets, export_targets
from tom_dataproducts.alertstreams.hermes import BuildHermesMessage, preload_to_hermes
Expand Down
2 changes: 1 addition & 1 deletion tom_targets/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from rest_framework import viewsets, permissions

from tom_targets.models import Target, PersistentShare
from tom_targets.serializers import PersistentShareSerializer
from tom_targets.persistent_sharing_serializers import PersistentShareSerializer


class PSModelPermission(permissions.DjangoModelPermissions):
Expand Down
Loading