From c225e36a6d0b1b11d9f0e39f9a7a2462b2b3f353 Mon Sep 17 00:00:00 2001
From: valyo <582646+valyo@users.noreply.github.com>
Date: Tue, 26 Sep 2023 15:18:36 +0200
Subject: [PATCH 001/114] Add UpdateFailedFiles endpoint
---
dds_web/api/__init__.py | 1 +
dds_web/api/files.py | 13 +++++++++++++
2 files changed, 14 insertions(+)
diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py
index 3a7da23ff..55b3853b7 100644
--- a/dds_web/api/__init__.py
+++ b/dds_web/api/__init__.py
@@ -50,6 +50,7 @@ def output_json(data, code, headers=None):
api.add_resource(files.FileInfo, "/file/info", endpoint="file_info")
api.add_resource(files.FileInfoAll, "/file/all/info", endpoint="all_file_info")
api.add_resource(files.UpdateFile, "/file/update", endpoint="update_file")
+api.add_resource(files.UpdateFailedFiles, "/file/failed/update", endpoint="update_failed_file")
# Projects ############################################################################## Projects #
api.add_resource(project.UserProjects, "/proj/list", endpoint="list_projects")
diff --git a/dds_web/api/files.py b/dds_web/api/files.py
index 415977924..457535635 100644
--- a/dds_web/api/files.py
+++ b/dds_web/api/files.py
@@ -740,3 +740,16 @@ def put(self):
db.session.commit()
return {"message": "File info updated."}
+
+
+class UpdateFailedFiles(flask_restful.Resource):
+ """Add files from failed_delivery_log to DB using the "update_uploaded_file_with_log" command"""
+
+ @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"])
+ @json_required
+ @handle_validation_errors
+ def put(self):
+ """Run flask command with failed_delivery_log."""
+
+ flask.current_app.logger.debug("API called")
+ return {"message": "File(s) info updated."}
From 5e91e42c7891a712c075b5576ba9f41462519f9d Mon Sep 17 00:00:00 2001
From: valyo <582646+valyo@users.noreply.github.com>
Date: Tue, 26 Sep 2023 15:20:15 +0200
Subject: [PATCH 002/114] black
---
dds_web/api/files.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dds_web/api/files.py b/dds_web/api/files.py
index 457535635..386994f90 100644
--- a/dds_web/api/files.py
+++ b/dds_web/api/files.py
@@ -750,6 +750,6 @@ class UpdateFailedFiles(flask_restful.Resource):
@handle_validation_errors
def put(self):
"""Run flask command with failed_delivery_log."""
-
+
flask.current_app.logger.debug("API called")
return {"message": "File(s) info updated."}
From 833a87459d3584616e99cb55ff200fee63c5b160 Mon Sep 17 00:00:00 2001
From: valyo <582646+valyo@users.noreply.github.com>
Date: Thu, 5 Oct 2023 11:15:23 +0200
Subject: [PATCH 003/114] created utils function which can be called by the
command and the new endpoint
---
dds_web/api/files.py | 10 ++++++--
dds_web/commands.py | 53 ++---------------------------------------
dds_web/utils.py | 56 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 66 insertions(+), 53 deletions(-)
diff --git a/dds_web/api/files.py b/dds_web/api/files.py
index 386994f90..56e0e568e 100644
--- a/dds_web/api/files.py
+++ b/dds_web/api/files.py
@@ -751,5 +751,11 @@ class UpdateFailedFiles(flask_restful.Resource):
def put(self):
"""Run flask command with failed_delivery_log."""
- flask.current_app.logger.debug("API called")
- return {"message": "File(s) info updated."}
+ # Verify project ID and access
+ project = project_schemas.ProjectRequiredSchema().load(flask.request.args)
+
+ # Get the request json and pass it to add_uploaded_files_to_db
+ request_json = flask.request.get_json(silent=True)
+ dds_web.utils.add_uploaded_files_to_db(project, request_json)
+
+ return {"message": "File(s) added to database."}
diff --git a/dds_web/commands.py b/dds_web/commands.py
index c9d925cb3..343cbe6f2 100644
--- a/dds_web/commands.py
+++ b/dds_web/commands.py
@@ -211,8 +211,7 @@ def update_uploaded_file_with_log(project, path_to_log_file):
"""Update file details that weren't properly uploaded to db from cli log"""
import botocore
from dds_web.database import models
- from dds_web import db
- from dds_web.api.api_s3_connector import ApiS3Connector
+ from dds_web import utils
import json
proj_in_db = models.Project.query.filter_by(public_id=project).one_or_none()
@@ -226,56 +225,8 @@ def update_uploaded_file_with_log(project, path_to_log_file):
with open(path_to_log_file, "r") as f:
log = json.load(f)
- errors = {}
- files_added = []
- for file, vals in log.items():
- status = vals.get("status")
- if not status or not status.get("failed_op") == "add_file_db":
- continue
-
- with ApiS3Connector(project=proj_in_db) as s3conn:
- try:
- _ = s3conn.resource.meta.client.head_object(
- Bucket=s3conn.project.bucket, Key=vals["path_remote"]
- )
- except botocore.client.ClientError as err:
- if err.response["Error"]["Code"] == "404":
- errors[file] = {"error": "File not found in S3", "traceback": err.__traceback__}
- else:
- file_object = models.File.query.filter(
- sqlalchemy.and_(
- models.File.name == sqlalchemy.func.binary(file),
- models.File.project_id == proj_in_db.id,
- )
- ).first()
- if file_object:
- errors[file] = {"error": "File already in database."}
- else:
- new_file = models.File(
- name=file,
- name_in_bucket=vals["path_remote"],
- subpath=vals["subpath"],
- project_id=proj_in_db.id,
- size_original=vals["size_raw"],
- size_stored=vals["size_processed"],
- compressed=not vals["compressed"],
- public_key=vals["public_key"],
- salt=vals["salt"],
- checksum=vals["checksum"],
- )
- new_version = models.Version(
- size_stored=new_file.size_stored, time_uploaded=datetime.datetime.utcnow()
- )
- proj_in_db.file_versions.append(new_version)
- proj_in_db.files.append(new_file)
- new_file.versions.append(new_version)
-
- db.session.add(new_file)
- files_added.append(new_file)
- db.session.commit()
- flask.current_app.logger.info(f"Files added: {files_added}")
- flask.current_app.logger.info(f"Errors while adding files: {errors}")
+ utils.add_uploaded_files_to_db(proj_in_db, log)
@click.group(name="lost-files")
diff --git a/dds_web/utils.py b/dds_web/utils.py
index eb34ef5a4..0057edef2 100644
--- a/dds_web/utils.py
+++ b/dds_web/utils.py
@@ -749,3 +749,59 @@ def use_sto4(unit_object, project_object) -> bool:
flask.current_app.logger.info(f"{project_id_logging}sto2")
return False
+
+
+def add_uploaded_files_to_db(proj_in_db, log):
+ from dds_web import db
+ from dds_web.api.api_s3_connector import ApiS3Connector
+
+ errors = {}
+ files_added = []
+ for file, vals in log.items():
+ status = vals.get("status")
+ if not status or not status.get("failed_op") == "add_file_db":
+ continue
+
+ with ApiS3Connector(project=proj_in_db) as s3conn:
+ try:
+ _ = s3conn.resource.meta.client.head_object(
+ Bucket=s3conn.project.bucket, Key=vals["path_remote"]
+ )
+ except botocore.client.ClientError as err:
+ if err.response["Error"]["Code"] == "404":
+ errors[file] = {"error": "File not found in S3", "traceback": err.__traceback__}
+ else:
+ file_object = models.File.query.filter(
+ sqlalchemy.and_(
+ models.File.name == sqlalchemy.func.binary(file),
+ models.File.project_id == proj_in_db.id,
+ )
+ ).first()
+ if file_object:
+ errors[file] = {"error": "File already in database."}
+ else:
+ new_file = models.File(
+ name=file,
+ name_in_bucket=vals["path_remote"],
+ subpath=vals["subpath"],
+ project_id=proj_in_db.id,
+ size_original=vals["size_raw"],
+ size_stored=vals["size_processed"],
+ compressed=not vals["compressed"],
+ public_key=vals["public_key"],
+ salt=vals["salt"],
+ checksum=vals["checksum"],
+ )
+ new_version = models.Version(
+ size_stored=new_file.size_stored, time_uploaded=datetime.datetime.utcnow()
+ )
+ proj_in_db.file_versions.append(new_version)
+ proj_in_db.files.append(new_file)
+ new_file.versions.append(new_version)
+
+ db.session.add(new_file)
+ files_added.append(new_file)
+ db.session.commit()
+
+ flask.current_app.logger.info(f"Files added: {files_added}")
+ flask.current_app.logger.info(f"Errors while adding files: {errors}")
From 9364613c8d835eff828eb8ef29e2ea74fe3f0120 Mon Sep 17 00:00:00 2001
From: valyo <582646+valyo@users.noreply.github.com>
Date: Thu, 5 Oct 2023 15:35:09 +0200
Subject: [PATCH 004/114] add test for the new utils function
---
tests/test_utils.py | 49 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 48 insertions(+), 1 deletion(-)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index bc6864db7..51dca7c83 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,7 +1,7 @@
import marshmallow
from dds_web import utils
import pytest
-from unittest.mock import patch
+from unittest.mock import patch, MagicMock
from unittest.mock import PropertyMock
from dds_web import db
@@ -1510,3 +1510,50 @@ def test_use_sto4_return_true(client: flask.testing.FlaskClient):
# Run function
result: bool = use_sto4(unit_object=unit, project_object=project)
assert result is True
+
+ def test_add_uploaded_files_to_db(self, mock_session, mock_version, mock_query, mock_logger):
+ # Mock input data
+ proj_in_db = MagicMock()
+ log = {
+ "file1.txt": {
+ "status": {"failed_op": "add_file_db"},
+ "path_remote": "path/to/file1.txt",
+ "subpath": "subpath",
+ "size_raw": 100,
+ "size_processed": 200,
+ "compressed": False,
+ "public_key": "public_key",
+ "salt": "salt",
+ "checksum": "checksum",
+ }
+ }
+
+ # Mock S3 connection
+ mock_s3conn = MagicMock()
+ mock_resource = MagicMock()
+ mock_client = MagicMock()
+ mock_s3conn.__enter__.return_value = mock_resource
+ mock_resource.meta.client.head_object.return_value = None
+ ApiS3Connector.return_value = mock_s3conn
+
+ # Mock database query
+ mock_file = MagicMock()
+ mock_file = mock_query.filter.return_value.first.return_value
+
+ # Call the function
+ add_uploaded_files_to_db(proj_in_db, log)
+
+ # Assert database operations
+ mock_query.filter.assert_called_once_with(
+ File.name == sqlalchemy.func.binary("file1.txt"), File.project_id == proj_in_db.id
+ )
+ mock_file.versions.append.assert_called_once_with(mock_version.return_value)
+ proj_in_db.file_versions.append.assert_called_once_with(mock_version.return_value)
+ proj_in_db.files.append.assert_called_once_with(mock_file)
+ mock_session.add.assert_called_once_with(mock_file)
+ mock_session.commit.assert_called_once()
+
+ # Assert logs
+ mock_logger.assert_any_call("vals: {'status': {'failed_op': 'add_file_db'}}")
+ mock_logger.assert_any_call("Files added: [ The
- project {{project_id}} is now available for your access in the SciLifeLab Data Delivery System (DDS).
- The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple
- way.
+ The following project is now available for your access in the SciLifeLab Data Delivery System (DDS) and you can now download your data.
+
{% if unit_email %}
You were added to this project on behalf of {{displayed_sender}} ({{unit_email}}).
@@ -28,6 +30,10 @@
The DDS CLI command dds data get -p {{project_id}} -a can be used to download all the files in this project to your current directory.
Your access to this project will expire on {{deadline}}
+ What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple
+ way.
- The following project is now available for your access in the SciLifeLab Data Delivery System (DDS) and you can now download your data.
+ The following project is now available for your access in the SciLifeLab Data Delivery System (DDS) and you can download your data.
Your access to this project will expire on {{deadline}}
- What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple
+ What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple
way.
The DDS CLI command dds ls -p {{project_id}} can be used to list the files in this project.
- The DDS CLI command dds data get -p {{project_id}} -a can be used to download all the files in this project to your current directory.
Your access to this project will expire on {{deadline}}
diff --git a/dds_web/templates/mail/project_release.txt b/dds_web/templates/mail/project_release.txt
index a7cf4774c..162f0a803 100644
--- a/dds_web/templates/mail/project_release.txt
+++ b/dds_web/templates/mail/project_release.txt
@@ -10,7 +10,7 @@ You were added to this project by {{displayed_sender}}.
The DDS CLI command 'dds ls -p {{project_id}}' can be used to list the files in this project.
-The DDS CLI command 'dds data get -p {{project_id}} -a' can be used to download all the files in this project to your current directory.
+The DDS CLI command 'dds data get -p {{project_id}} -a --verify-checksum' can be used to download all the files in this project to your current directory.
Your access to this project will expire on {{deadline}}
From 01cbbeaed0e0090e97e8e64a1092d67258c1b2fe Mon Sep 17 00:00:00 2001
From: rv0lt
+ You were added to this project on behalf of {{displayed_sender}}.
+
+ To list the files in this project, run:
+ To download all the files in this project to your current directory, run:
+ For more information (including an installation guide), see the DDS CLI documentation here: scilifelabdatacentre.github.io/dds_cli.
+
{% if unit_email %}
- You were added to this project on behalf of {{displayed_sender}} ({{unit_email}}).
+ if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at ({{unit_email}}).
{% else %}
- You were added to this project by {{displayed_sender}}.
+ if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}.
{% endif %}
- The DDS CLI command dds ls -p {{project_id}} can be used to list the files in this project.
- The DDS CLI command dds data get -p {{project_id}} -a --verify-checksum can be used to download all the files in this project to your current directory.
- Your access to this project will expire on {{deadline}}
+ Your access to this project will expire on: {{deadline}}
What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple
way.
Your access to this project will expire on: {{deadline}}
- What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple
- way.
From 6cebad3a270173d9a7e21d28bdd4bcedda6f2ce2 Mon Sep 17 00:00:00 2001
From: rv0lt
-
+
+
diff --git a/dds_web/templates/mail/project_release.txt b/dds_web/templates/mail/project_release.txt
index 162f0a803..e8d88dda9 100644
--- a/dds_web/templates/mail/project_release.txt
+++ b/dds_web/templates/mail/project_release.txt
@@ -2,16 +2,22 @@ The following project is now available for your access in the SciLifeLab Data De
- Project Title: {{project_title}}
- DDS project ID: {{project_id}}
+You were added to this project on behalf of {{displayed_sender}}.
+
+To list the files in this project, run:
+ dds ls -p {{project_id}}
+
+To download all the files in this project to your current directory, run:
+ dds data get -p {{project_id}} -a --verify-checksum.
+
+For more information (including an installation guide), see the DDS CLI documentation here: https://scilifelabdatacentre.github.io/dds_cli/
+
{% if unit_email %}
-You were added to this project on behalf of {{displayed_sender}} ({{unit_email}}).
+if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}at ({{unit_email}}).
{% else %}
-You were added to this project by {{displayed_sender}}.
+if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}.
{% endif %}
-The DDS CLI command 'dds ls -p {{project_id}}' can be used to list the files in this project.
-
-The DDS CLI command 'dds data get -p {{project_id}} -a --verify-checksum' can be used to download all the files in this project to your current directory.
-
Your access to this project will expire on {{deadline}}
What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way.
From b143738068c93d316e195641d8d49f9ec7416831 Mon Sep 17 00:00:00 2001
From: rv0lt
From 3584f6bfc9afc34922259e4b923f3b349d61d6a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Revuelta?=
<46089290+rv0lt@users.noreply.github.com>
Date: Fri, 6 Oct 2023 11:01:10 +0200
Subject: [PATCH 036/114] Update SPRINTLOG.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com>
---
SPRINTLOG.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/SPRINTLOG.md b/SPRINTLOG.md
index 2fa6fffef..09dc78670 100644
--- a/SPRINTLOG.md
+++ b/SPRINTLOG.md
@@ -310,6 +310,5 @@ _Nothing merged in CLI during this sprint_
# 2023-10-02 - 2023-10-13
-- Use full DDS name in MOTD email subject ([#1422])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1422)
-- Added the project title aling with the internal project ID in the email sent when a project is released ([#1537])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1537)
-- Project title displayed along with the internal project ID email sent when a project is released ([#1537])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1537)
+- Project title displayed along with the internal project ID email sent when a project is released ([#1475](https://github.com/ScilifelabDataCentre/dds_web/pull/1475))
+- Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477))
From 458bc1fe788ac838afd99c1942eba41db3fe2f71 Mon Sep 17 00:00:00 2001
From: rv0lt
+ dds ls -p {{project_id}}.
+ dds data get -p {{project_id}} -a --verify-checksum.
{% if unit_email %}
- if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at ({{unit_email}}).
+ if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}.
{% else %}
if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}.
{% endif %}
From 4cefdabd133e0f2d2a126182ebfdf8d65bc87a1f Mon Sep 17 00:00:00 2001
From: rv0lt
To list the files in this project, run:
- dds ls -p {{project_id}}.
dds ls -p {{project_id}}
.
+
To download all the files in this project to your current directory, run:
- dds data get -p {{project_id}} -a --verify-checksum.
dds data get -p {{project_id}} -a --verify-checksum
.
For more information (including an installation guide), see the DDS CLI documentation here: scilifelabdatacentre.github.io/dds_cli.
@@ -37,7 +38,7 @@ {% endif %}
- Your access to this project will expire on: {{deadline}}
+ Your access to this project will expire on:What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way.
From 86829582a3d152da8548500fed4a84fde7cf688f Mon Sep 17 00:00:00 2001 From: rv0lt- You were added to this project on behalf of {{displayed_sender}}. + You were added to this project {% if unit_email %} on behalf of {% else %} by {% endif %} {{displayed_sender}}.
To list the files in this project, run:
From bf5f8dc0860c119f5bc751b89c7e2c443b09fbfe Mon Sep 17 00:00:00 2001
From: rv0lt
To list the files in this project, run:
- dds ls -p {{project_id}}
.
dds ls -p {{project_id}}
To download all the files in this project to your current directory, run:
- dds data get -p {{project_id}} -a --verify-checksum
.
dds data get -p {{project_id}} -a --verify-checksum
For more information (including an installation guide), see the DDS CLI documentation here: scilifelabdatacentre.github.io/dds_cli.
From c49e7d6219dd35c5fa2c210baf05501f1029d048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:20:40 +0200 Subject: [PATCH 050/114] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 25dbfe448..af605b7ad 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -22,7 +22,7 @@
To list the files in this project, run:
- dds ls -p {{project_id}}
dds ls -p {{project_id}}
To download all the files in this project to your current directory, run:
From e1761ec048ee89f02c84bc8ad4558b6c9a80dc49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Revuelta?=
<46089290+rv0lt@users.noreply.github.com>
Date: Tue, 10 Oct 2023 14:21:08 +0200
Subject: [PATCH 051/114] Update dds_web/templates/mail/project_release.html
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com>
---
dds_web/templates/mail/project_release.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html
index af605b7ad..307be8dd7 100644
--- a/dds_web/templates/mail/project_release.html
+++ b/dds_web/templates/mail/project_release.html
@@ -26,7 +26,7 @@
To download all the files in this project to your current directory, run:
- dds data get -p {{project_id}} -a --verify-checksum
dds data get -p {{project_id}} -a --verify-checksum
For more information (including an installation guide), see the DDS CLI documentation here: scilifelabdatacentre.github.io/dds_cli.
From 7a36d53e0f5b5c8bca7d09bdf54291b4b8e569d3 Mon Sep 17 00:00:00 2001
From: rv0lt
{% if unit_email %}
- if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}.
+ If you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}.
{% else %}
if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}.
{% endif %}
From 7724d7509b4133ca499fe2492da116599b4df065 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Revuelta?=
<46089290+rv0lt@users.noreply.github.com>
Date: Tue, 10 Oct 2023 14:34:25 +0200
Subject: [PATCH 054/114] Update dds_web/templates/mail/project_release.html
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com>
---
dds_web/templates/mail/project_release.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html
index 701ad8b21..06d536664 100644
--- a/dds_web/templates/mail/project_release.html
+++ b/dds_web/templates/mail/project_release.html
@@ -34,7 +34,7 @@
{% if unit_email %}
If you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}.
{% else %}
- if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}.
+ If you experience issues, please contact the SciLifeLab unit {{displayed_sender}}.
{% endif %}
From 5404755eb0d9d6b2121e5c0bb617bdf1f5cb1cf2 Mon Sep 17 00:00:00 2001
From: rv0lt