diff --git a/controlpanel/api/migrations/0050_alter_tool_deprecated_message.py b/controlpanel/api/migrations/0050_alter_tool_deprecated_message.py
new file mode 100644
index 000000000..8a7a2409a
--- /dev/null
+++ b/controlpanel/api/migrations/0050_alter_tool_deprecated_message.py
@@ -0,0 +1,21 @@
+# Generated by Django 5.1.2 on 2024-12-09 15:07
+
+# Third-party
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("api", "0049_tool_deprecated_message_tool_is_retired"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="tool",
+ name="deprecated_message",
+ field=models.TextField(
+ blank=True, help_text="If no message is provided, a default message will be used."
+ ),
+ ),
+ ]
diff --git a/controlpanel/api/models/tool.py b/controlpanel/api/models/tool.py
index 1303fbc2a..36560403e 100644
--- a/controlpanel/api/models/tool.py
+++ b/controlpanel/api/models/tool.py
@@ -27,6 +27,7 @@ class Tool(TimeStampedModel):
"rstudio": "rstudio",
"vscode": "vscode",
}
+ DEFAULT_DEPRECATED_MESSAGE = "The selected release has been deprecated and will be retired soon. Please update to a more recent version." # noqa
description = models.TextField(blank=True)
chart_name = models.CharField(max_length=100, blank=False)
@@ -52,7 +53,9 @@ class Tool(TimeStampedModel):
)
is_deprecated = models.BooleanField(default=False)
- deprecated_message = models.TextField(blank=True)
+ deprecated_message = models.TextField(
+ blank=True, help_text="If no message is provided, a default message will be used."
+ )
is_retired = models.BooleanField(default=False)
class Meta(TimeStampedModel.Meta):
@@ -83,6 +86,16 @@ def image_tag(self):
"{}.image.tag".format(chart_image_key_name)
)
+ @property
+ def get_deprecated_message(self):
+ if not self.is_deprecated:
+ return ""
+
+ if self.is_retired:
+ return ""
+
+ return self.deprecated_message or self.DEFAULT_DEPRECATED_MESSAGE
+
class ToolDeploymentManager:
"""
diff --git a/controlpanel/frontend/jinja2/release-detail.html b/controlpanel/frontend/jinja2/release-detail.html
index 0cd63c6c0..d74dea699 100644
--- a/controlpanel/frontend/jinja2/release-detail.html
+++ b/controlpanel/frontend/jinja2/release-detail.html
@@ -153,7 +153,7 @@
{{ page_title }}
},
"classes": "govuk-!-width-two-thirds",
"hint": {
- "text": 'A flag to indicate the release is deprecated and will soon become unavailable to deploy'
+ "text": 'Checking this will display a deprecation message to users when they select this release'
},
"name": "is_deprecated",
"items": [
@@ -172,7 +172,7 @@
{{ page_title }}
},
"classes": "govuk-!-width-two-thirds",
"hint": {
- "text": 'A message to display to users if they select this tool'
+ "text": form.deprecated_message.help_text
},
"name": "deprecated_message",
"value": form.deprecated_message.value(),
@@ -188,7 +188,7 @@
{{ page_title }}
},
"classes": "govuk-!-width-two-thirds",
"hint": {
- "text": 'A flag to indicate the release is retired. Users will not be able to deploy this release.'
+ "text": 'Checking this will remove this release from all users dropdown options on the Your Tools page.'
},
"name": "is_retired",
"items": [
diff --git a/controlpanel/frontend/jinja2/tool-list.html b/controlpanel/frontend/jinja2/tool-list.html
index 8648aecf1..3da29b75c 100644
--- a/controlpanel/frontend/jinja2/tool-list.html
+++ b/controlpanel/frontend/jinja2/tool-list.html
@@ -41,16 +41,22 @@
{{ tool_info.name }}
{% if deployment and deployment.tool_id > -1 %}
{% set installed_chart_version = deployment.description %}
{% set installed_release_version = deployment.tool_id %}
-
{% else %}
{% endif %}
{% for release_version, release_detail in tool_info["releases"].items(): %}
{% if release_version != installed_release_version: %}
-
{% endif %}
{% endfor %}
@@ -107,6 +113,8 @@
{{ tool_info.name }}
+
{{ deployment.deprecated_message }}
+
{% if deployment and deployment.tool_id == -1 %}
diff --git a/controlpanel/frontend/static/javascripts/modules/tool-status.js b/controlpanel/frontend/static/javascripts/modules/tool-status.js
index 2121c4b35..ddc61b8e9 100644
--- a/controlpanel/frontend/static/javascripts/modules/tool-status.js
+++ b/controlpanel/frontend/static/javascripts/modules/tool-status.js
@@ -156,5 +156,21 @@ moj.Modules.toolStatus = {
// If "(not installed)" or "(installed)" version selected
// the "Deploy" button needs to be disabled
deployButton.disabled = notInstalledSelected || installedSelected;
+
+ this.toggleDeprecationMessage(selected, targetTool);
},
+
+ toggleDeprecationMessage(selected, targetTool) {
+ const isDeprecated = selected.attributes["data-is-deprecated"].value === "True";
+ const deprecationMessageElement = document.getElementById(targetTool.value + "-deprecation-message");
+ const deprecationMessage = selected.attributes["data-deprecated-message"].value;
+
+ if (isDeprecated) {
+ deprecationMessageElement.firstChild.innerText = deprecationMessage;
+ deprecationMessageElement.classList.remove(this.hidden);
+ } else {
+ deprecationMessageElement.classList.add(this.hidden);
+ deprecationMessageElement.firstChild.innerText = "";
+ }
+ }
};
diff --git a/controlpanel/frontend/views/tool.py b/controlpanel/frontend/views/tool.py
index 3cd3f118f..a78e416a1 100644
--- a/controlpanel/frontend/views/tool.py
+++ b/controlpanel/frontend/views/tool.py
@@ -71,13 +71,15 @@ def _find_related_tool_record(self, chart_name, chart_version, image_tag):
"""
tool_set = Tool.objects.filter(
chart_name=chart_name, version=chart_version, is_restricted=False
- )
- for item in tool_set:
- if item.image_tag == image_tag:
- return item
- return tool_set.first()
+ ).exclude(is_retired=True)
+ for tool in tool_set:
+ if tool.image_tag == image_tag:
+ return tool
+ # If we cant find a tool with the same image tag, this must mean that it was retired or
+ # deleted. So return none, and let the calling function handle it
+ return None
- def _add_new_item_to_tool_box(self, user, tool_box, tool, tools_info, charts_info):
+ def _add_new_item_to_tool_box(self, user, tool_box, tool, tools_info):
if tool_box not in tools_info:
tools_info[tool_box] = {
"name": tool.name,
@@ -85,16 +87,19 @@ def _add_new_item_to_tool_box(self, user, tool_box, tool, tools_info, charts_inf
"deployment": None,
"releases": {},
}
- image_tag = tool.image_tag
- if not image_tag:
- image_tag = charts_info.get(tool.version, {}) or "unknown"
+ # TODO We should update model to always store an image tag
+ # image_tag = tool.image_tag
+ # if not image_tag:
+ # image_tag = charts_info.get(tool.version, {}) or "unknown"
if tool.id not in tools_info[tool_box]["releases"]:
tools_info[tool_box]["releases"][tool.id] = {
"tool_id": tool.id,
"chart_name": tool.chart_name,
"description": tool.description,
"chart_version": tool.version,
- "image_tag": image_tag,
+ "image_tag": tool.image_tag,
+ "is_deprecated": tool.is_deprecated,
+ "deprecated_message": tool.get_deprecated_message,
}
def _get_tool_deployed_image_tag(self, containers):
@@ -103,8 +108,10 @@ def _get_tool_deployed_image_tag(self, containers):
return container.image.split(":")[1]
return None
- def _add_deployed_charts_info(self, tools_info, user, id_token, charts_info):
+ def _add_deployed_charts_info(self, tools_info, user, id_token):
# Get list of deployed tools
+ # TODO this sets what tool the user currently has deployed. If we were to refactor to store
+ # deployed tools in the database, we could remove a lot of this logic
deployments = cluster.ToolDeployment.get_deployments(user, id_token)
for deployment in deployments:
chart_name, chart_version = deployment.metadata.labels["chart"].rsplit("-", 1)
@@ -119,7 +126,7 @@ def _add_deployed_charts_info(self, tools_info, user, id_token, charts_info):
)
)
else:
- self._add_new_item_to_tool_box(user, tool_box, tool, tools_info, charts_info)
+ self._add_new_item_to_tool_box(user, tool_box, tool, tools_info)
if tool_box not in tools_info:
# up to this stage, if the tool_box is still empty, it means
# there is no tool release available in db
@@ -131,21 +138,26 @@ def _add_deployed_charts_info(self, tools_info, user, id_token, charts_info):
"image_tag": image_tag,
"description": tool.description if tool else "Not available",
"status": ToolDeployment(tool, user).get_status(id_token, deployment=deployment),
+ "is_deprecated": tool.is_deprecated if tool else False,
+ "deprecated_message": tool.get_deprecated_message if tool else "",
+ "is_retired": tool is None,
}
- def _retrieve_detail_tool_info(self, user, tools, charts_info):
+ def _retrieve_detail_tool_info(self, user, tools):
+ # TODO why do we need this? We could change so that all information required about available tools comes from the DB # noqa: E501
tools_info = {}
for tool in tools:
# Work out which bucket the chart should be in
tool_box = self._locate_tool_box_by_chart_name(tool.chart_name)
# No matching tool bucket for the given chart. So ignore.
if tool_box:
- self._add_new_item_to_tool_box(user, tool_box, tool, tools_info, charts_info)
+ self._add_new_item_to_tool_box(user, tool_box, tool, tools_info)
return tools_info
def _get_charts_info(self, tool_list):
# We may need the default image_tag from helm chart
# unless we configure it specifically in parameters of tool release
+ # TODO if we make sure that we always have an image_tag for a tool, then building charts_info is redundant and could be removed # noqa: E501
charts_info = {}
chart_entries = None
for tool in tool_list:
@@ -229,14 +241,14 @@ def get_context_data(self, *args, **kwargs):
context["managed_airflow_prod_url"] = f"{settings.AWS_SERVICE_URL}/?{args_airflow_prod_url}"
# Arrange tools information
- charts_info = self._get_charts_info(context["tools"])
- tools_info = self._retrieve_detail_tool_info(user, context["tools"], charts_info)
+ # charts_info = self._get_charts_info(context["tools"])
+ tools_info = self._retrieve_detail_tool_info(user, context["tools"])
if "vscode" in tools_info:
url = tools_info["vscode"]["url"]
tools_info["vscode"]["url"] = f"{url}?folder=/home/analyticalplatform/workspace"
- self._add_deployed_charts_info(tools_info, user, id_token, charts_info)
+ self._add_deployed_charts_info(tools_info, user, id_token)
context["tools_info"] = tools_info
return context