Repository: apache/streampipes
Pull request #2986 (2024-07-01 14:23:36)

refactor(#2964): timestamp conversion is broken in adapters
<!--
  ~ Licensed to the Apache Software Foundation (ASF) under one or more
  ~ contributor license agreements.  See the NOTICE file distributed with
  ~ this work for additional information regarding copyright ownership.
  ~ The ASF licenses this file to You under the Apache License, Version 2.0
  ~ (the "License"); you may not use this file except in compliance with
  ~ the License.  You may obtain a copy of the License at
  ~
  ~    http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  ~
  -->

  <!--
Thanks for contributing! Here are some tips you can follow to help us incorporate your contribution quickly and easily:
1. If this is your first time, please read our contributor guidelines:
    - https://streampipes.apache.org/community/get-involved/
    - https://cwiki.apache.org/confluence/display/STREAMPIPES/Getting+Started
2. Make sure the PR title is formatted like: `[#<GitHub issue id>] PR title ...`
3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., `[WIP][#<GitHub issue id>] PR title ...`.
4. Please write your PR title to summarize what this PR proposes/fixes.
5. Link the PR to the corresponding GitHub issue (if present) in the `Development` section in the right menu bar.
6. Be sure to keep the PR description updated to reflect all changes.
7. If possible, provide a concise example to reproduce the issue for a faster review.
8. Make sure tests pass via `mvn clean install`.
9. (Optional) If the contribution is large, please file an Apache ICLA
    - http://apache.org/licenses/icla.pdf
-->

### Purpose
<!--
Please clarify what changes you are proposing and describe how those changes will address the issue.
Furthermore, describe potential consequences the changes might have.
-->

This PR includes:

- Refactoring of the timestamp transformation rule in adapters
- Fix for the timestamp rule related to string regex expressions.
- Added tests for timestamp transformation rules with Unix timestamps in seconds and milliseconds (currently commented out, to be covered in a separate PR as this is already quite large).

### Remarks
<!--
Is there anything left we need to pay attention on?
Are there some references that might be important? E.g. links to Confluence, or discussions
on the mailing list or GitHub.
-->
PR introduces (a) breaking change(s): no

PR introduces (a) deprecation(s): no



Patch:
diff --git a/streampipes-client-python/poetry.lock b/streampipes-client-python/poetry.lock
index 1cccd6e5a4..55a7989f05 100644
--- a/streampipes-client-python/poetry.lock
+++ b/streampipes-client-python/poetry.lock
@@ -2456,109 +2456,118 @@ files = [
 
 [[package]]
 name = "pydantic"
-version = "2.7.0"
+version = "2.8.0"
 description = "Data validation using Python type hints"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"},
-    {file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"},
+    {file = "pydantic-2.8.0-py3-none-any.whl", hash = "sha256:ead4f3a1e92386a734ca1411cb25d94147cf8778ed5be6b56749047676d6364e"},
+    {file = "pydantic-2.8.0.tar.gz", hash = "sha256:d970ffb9d030b710795878940bd0489842c638e7252fc4a19c3ae2f7da4d6141"},
 ]
 
 [package.dependencies]
 annotated-types = ">=0.4.0"
-pydantic-core = "2.18.1"
-typing-extensions = ">=4.6.1"
+pydantic-core = "2.20.0"
+typing-extensions = {version = ">=4.6.1", markers = "python_version < \"3.13\""}
 
 [package.extras]
 email = ["email-validator (>=2.0.0)"]
 
 [[package]]
 name = "pydantic-core"
-version = "2.18.1"
+version = "2.20.0"
 description = "Core functionality for Pydantic validation and serialization"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"},
-    {file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"},
-    {file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"},
-    {file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"},
-    {file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"},
-    {file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"},
-    {file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"},
-    {file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"},
-    {file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"},
-    {file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"},
-    {file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"},
-    {file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"},
-    {file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"},
-    {file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"},
-    {file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"},
-    {file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"},
-    {file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"},
-    {file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"},
-    {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"},
-    {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"},
-    {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"},
-    {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"},
-    {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"},
-    {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"},
-    {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"},
-    {file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"},
-    {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"},
-    {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"},
-    {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"},
-    {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"},
-    {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"},
-    {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"},
-    {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"},
-    {file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"},
-    {file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e9dcd7fb34f7bfb239b5fa420033642fff0ad676b765559c3737b91f664d4fa9"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:649a764d9b0da29816889424697b2a3746963ad36d3e0968784ceed6e40c6355"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7701df088d0b05f3460f7ba15aec81ac8b0fb5690367dfd072a6c38cf5b7fdb5"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab760f17c3e792225cdaef31ca23c0aea45c14ce80d8eff62503f86a5ab76bff"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb1ad5b4d73cde784cf64580166568074f5ccd2548d765e690546cff3d80937d"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b81ec2efc04fc1dbf400647d4357d64fb25543bae38d2d19787d69360aad21c9"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4a9732a5cad764ba37f3aa873dccb41b584f69c347a57323eda0930deec8e10"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6dc85b9e10cc21d9c1055f15684f76fa4facadddcb6cd63abab702eb93c98943"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:21d9f7e24f63fdc7118e6cc49defaab8c1d27570782f7e5256169d77498cf7c7"},
+    {file = "pydantic_core-2.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b315685832ab9287e6124b5d74fc12dda31e6421d7f6b08525791452844bc2d"},
+    {file = "pydantic_core-2.20.0-cp310-none-win32.whl", hash = "sha256:c3dc8ec8b87c7ad534c75b8855168a08a7036fdb9deeeed5705ba9410721c84d"},
+    {file = "pydantic_core-2.20.0-cp310-none-win_amd64.whl", hash = "sha256:85770b4b37bb36ef93a6122601795231225641003e0318d23c6233c59b424279"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:58e251bb5a5998f7226dc90b0b753eeffa720bd66664eba51927c2a7a2d5f32c"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:78d584caac52c24240ef9ecd75de64c760bbd0e20dbf6973631815e3ef16ef8b"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5084ec9721f82bef5ff7c4d1ee65e1626783abb585f8c0993833490b63fe1792"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d0f52684868db7c218437d260e14d37948b094493f2646f22d3dda7229bbe3f"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1def125d59a87fe451212a72ab9ed34c118ff771e5473fef4f2f95d8ede26d75"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34480fd6778ab356abf1e9086a4ced95002a1e195e8d2fd182b0def9d944d11"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d42669d319db366cb567c3b444f43caa7ffb779bf9530692c6f244fc635a41eb"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53b06aea7a48919a254b32107647be9128c066aaa6ee6d5d08222325f25ef175"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1f038156b696a1c39d763b2080aeefa87ddb4162c10aa9fabfefffc3dd8180fa"},
+    {file = "pydantic_core-2.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3f0f3a4a23717280a5ee3ac4fb1f81d6fde604c9ec5100f7f6f987716bb8c137"},
+    {file = "pydantic_core-2.20.0-cp311-none-win32.whl", hash = "sha256:316fe7c3fec017affd916a0c83d6f1ec697cbbbdf1124769fa73328e7907cc2e"},
+    {file = "pydantic_core-2.20.0-cp311-none-win_amd64.whl", hash = "sha256:2d06a7fa437f93782e3f32d739c3ec189f82fca74336c08255f9e20cea1ed378"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d6f8c49657f3eb7720ed4c9b26624063da14937fc94d1812f1e04a2204db3e17"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad1bd2f377f56fec11d5cfd0977c30061cd19f4fa199bf138b200ec0d5e27eeb"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed741183719a5271f97d93bbcc45ed64619fa38068aaa6e90027d1d17e30dc8d"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d82e5ed3a05f2dcb89c6ead2fd0dbff7ac09bc02c1b4028ece2d3a3854d049ce"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2ba34a099576234671f2e4274e5bc6813b22e28778c216d680eabd0db3f7dad"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:879ae6bb08a063b3e1b7ac8c860096d8fd6b48dd9b2690b7f2738b8c835e744b"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0eefc7633a04c0694340aad91fbfd1986fe1a1e0c63a22793ba40a18fcbdc8"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73deadd6fd8a23e2f40b412b3ac617a112143c8989a4fe265050fd91ba5c0608"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:35681445dc85446fb105943d81ae7569aa7e89de80d1ca4ac3229e05c311bdb1"},
+    {file = "pydantic_core-2.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0f6dd3612a3b9f91f2e63924ea18a4476656c6d01843ca20a4c09e00422195af"},
+    {file = "pydantic_core-2.20.0-cp312-none-win32.whl", hash = "sha256:7e37b6bb6e90c2b8412b06373c6978d9d81e7199a40e24a6ef480e8acdeaf918"},
+    {file = "pydantic_core-2.20.0-cp312-none-win_amd64.whl", hash = "sha256:7d4df13d1c55e84351fab51383520b84f490740a9f1fec905362aa64590b7a5d"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d43e7ab3b65e4dc35a7612cfff7b0fd62dce5bc11a7cd198310b57f39847fd6c"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b6a24d7b5893392f2b8e3b7a0031ae3b14c6c1942a4615f0d8794fdeeefb08b"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2f13c3e955a087c3ec86f97661d9f72a76e221281b2262956af381224cfc243"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72432fd6e868c8d0a6849869e004b8bcae233a3c56383954c228316694920b38"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d70a8ff2d4953afb4cbe6211f17268ad29c0b47e73d3372f40e7775904bc28fc"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e49524917b8d3c2f42cd0d2df61178e08e50f5f029f9af1f402b3ee64574392"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4f0f71653b1c1bad0350bc0b4cc057ab87b438ff18fa6392533811ebd01439c"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:16197e6f4fdecb9892ed2436e507e44f0a1aa2cff3b9306d1c879ea2f9200997"},
+    {file = "pydantic_core-2.20.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:763602504bf640b3ded3bba3f8ed8a1cc2fc6a87b8d55c1c5689f428c49c947e"},
+    {file = "pydantic_core-2.20.0-cp313-none-win32.whl", hash = "sha256:a3f243f318bd9523277fa123b3163f4c005a3e8619d4b867064de02f287a564d"},
+    {file = "pydantic_core-2.20.0-cp313-none-win_amd64.whl", hash = "sha256:03aceaf6a5adaad3bec2233edc5a7905026553916615888e53154807e404545c"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d6f2d8b8da1f03f577243b07bbdd3412eee3d37d1f2fd71d1513cbc76a8c1239"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a272785a226869416c6b3c1b7e450506152d3844207331f02f27173562c917e0"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efbb412d55a4ffe73963fed95c09ccb83647ec63b711c4b3752be10a56f0090b"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e4f46189d8740561b43655263a41aac75ff0388febcb2c9ec4f1b60a0ec12f3"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3df115f4a3c8c5e4d5acf067d399c6466d7e604fc9ee9acbe6f0c88a0c3cf"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a340d2bdebe819d08f605e9705ed551c3feb97e4fd71822d7147c1e4bdbb9508"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:616b9c2f882393d422ba11b40e72382fe975e806ad693095e9a3b67c59ea6150"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25c46bb2ff6084859bbcfdf4f1a63004b98e88b6d04053e8bf324e115398e9e7"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:23425eccef8f2c342f78d3a238c824623836c6c874d93c726673dbf7e56c78c0"},
+    {file = "pydantic_core-2.20.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:52527e8f223ba29608d999d65b204676398009725007c9336651c2ec2d93cffc"},
+    {file = "pydantic_core-2.20.0-cp38-none-win32.whl", hash = "sha256:1c3c5b7f70dd19a6845292b0775295ea81c61540f68671ae06bfe4421b3222c2"},
+    {file = "pydantic_core-2.20.0-cp38-none-win_amd64.whl", hash = "sha256:8093473d7b9e908af1cef30025609afc8f5fd2a16ff07f97440fd911421e4432"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ee7785938e407418795e4399b2bf5b5f3cf6cf728077a7f26973220d58d885cf"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e75794883d635071cf6b4ed2a5d7a1e50672ab7a051454c76446ef1ebcdcc91"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:344e352c96e53b4f56b53d24728217c69399b8129c16789f70236083c6ceb2ac"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:978d4123ad1e605daf1ba5e01d4f235bcf7b6e340ef07e7122e8e9cfe3eb61ab"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c05eaf6c863781eb834ab41f5963604ab92855822a2062897958089d1335dad"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc7e43b4a528ffca8c9151b6a2ca34482c2fdc05e6aa24a84b7f475c896fc51d"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658287a29351166510ebbe0a75c373600cc4367a3d9337b964dada8d38bcc0f4"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1dacf660d6de692fe351e8c806e7efccf09ee5184865893afbe8e59be4920b4a"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3e147fc6e27b9a487320d78515c5f29798b539179f7777018cedf51b7749e4f4"},
+    {file = "pydantic_core-2.20.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c867230d715a3dd1d962c8d9bef0d3168994ed663e21bf748b6e3a529a129aab"},
+    {file = "pydantic_core-2.20.0-cp39-none-win32.whl", hash = "sha256:22b813baf0dbf612752d8143a2dbf8e33ccb850656b7850e009bad2e101fc377"},
+    {file = "pydantic_core-2.20.0-cp39-none-win_amd64.whl", hash = "sha256:3a7235b46c1bbe201f09b6f0f5e6c36b16bad3d0532a10493742f91fbdc8035f"},
+    {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cafde15a6f7feaec2f570646e2ffc5b73412295d29134a29067e70740ec6ee20"},
+    {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2aec8eeea0b08fd6bc2213d8e86811a07491849fd3d79955b62d83e32fa2ad5f"},
+    {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:840200827984f1c4e114008abc2f5ede362d6e11ed0b5931681884dd41852ff1"},
+    {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ea1d8b7df522e5ced34993c423c3bf3735c53df8b2a15688a2f03a7d678800"},
+    {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5b8376a867047bf08910573deb95d3c8dfb976eb014ee24f3b5a61ccc5bee1b"},
+    {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d08264b4460326cefacc179fc1411304d5af388a79910832835e6f641512358b"},
+    {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7a3639011c2e8a9628466f616ed7fb413f30032b891898e10895a0a8b5857d6c"},
+    {file = "pydantic_core-2.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05e83ce2f7eba29e627dd8066aa6c4c0269b2d4f889c0eba157233a353053cea"},
+    {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:603a843fea76a595c8f661cd4da4d2281dff1e38c4a836a928eac1a2f8fe88e4"},
+    {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac76f30d5d3454f4c28826d891fe74d25121a346c69523c9810ebba43f3b1cec"},
+    {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e3b1d4b1b3f6082849f9b28427ef147a5b46a6132a3dbaf9ca1baa40c88609"},
+    {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2761f71faed820e25ec62eacba670d1b5c2709bb131a19fcdbfbb09884593e5a"},
+    {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a0586cddbf4380e24569b8a05f234e7305717cc8323f50114dfb2051fcbce2a3"},
+    {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:b8c46a8cf53e849eea7090f331ae2202cd0f1ceb090b00f5902c423bd1e11805"},
+    {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b4a085bd04af7245e140d1b95619fe8abb445a3d7fdf219b3f80c940853268ef"},
+    {file = "pydantic_core-2.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:116b326ac82c8b315e7348390f6d30bcfe6e688a7d3f1de50ff7bcc2042a23c2"},
+    {file = "pydantic_core-2.20.0.tar.gz", hash = "sha256:366be8e64e0cb63d87cf79b4e1765c0703dd6313c729b22e7b9e378db6b96877"},
 ]
 
 [package.dependencies]
@@ -3254,28 +3263,29 @@ files = [
 
 [[package]]
 name = "ruff"
-version = "0.4.0"
+version = "0.5.0"
 description = "An extremely fast Python linter and code formatter, written in Rust."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "ruff-0.4.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:70b8c620cf2212744eabd6d69c4f839f2be0d8880d27beaeb0adb6aa0b316aa8"},
-    {file = "ruff-0.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfa3e3ff53be05a8c5570c1585ea1e089f6b399ca99fcb78598d4a8234f248db"},
-    {file = "ruff-0.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5616cca501d1d16b932b7e607d7e1fd1b8c8c51d6ee484b7940fc1adc5bea541"},
-    {file = "ruff-0.4.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46eff08dd480b5d9b540846159fe134d70e3c45a3c913c600047cbf7f0e4e308"},
-    {file = "ruff-0.4.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d546f511431fff2b17adcf7110f3b2c2c0c8d33b0e10e5fd27fd340bc617efc"},
-    {file = "ruff-0.4.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c7b6b6b38e216036284c5779b6aa14acbf5664e3b5872533219cf93daf40ddfb"},
-    {file = "ruff-0.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e1cf8b064bb2a6b4922af7274fe2dffcb552d96ba716b2fbe5e2c970ed7de18"},
-    {file = "ruff-0.4.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9911c9046b94253e1fa844c0192bb764b86866a881502dee324686474d498c17"},
-    {file = "ruff-0.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ca7a971c8f1a0b6f5ff4a819c0d1c2619536530bbd5a289af725d8b2ef1013d"},
-    {file = "ruff-0.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:752e0f77f421141dd470a0b1bed4fd8f763aebabe32c80ed3580f740ef4ba807"},
-    {file = "ruff-0.4.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:84f2a5dd8f33964d826c5377e094f7ce11e55e432cd42d3bf64efe4384224a03"},
-    {file = "ruff-0.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0b20e7db4a672495320a8a18149b7febf4e4f97509a4657367144569ce0915fd"},
-    {file = "ruff-0.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0b0eddd339e24dc4f7719b1cde4967f6b6bc0ad948cc183711ba8910f14aeafe"},
-    {file = "ruff-0.4.0-py3-none-win32.whl", hash = "sha256:e70befd488271a2c28c80bd427f73d8855dd222fc549fa1e9967d287c5cfe781"},
-    {file = "ruff-0.4.0-py3-none-win_amd64.whl", hash = "sha256:8584b9361900997ccf8d7aaa4dc4ab43e258a853ca7189d98ac929dc9ee50875"},
-    {file = "ruff-0.4.0-py3-none-win_arm64.whl", hash = "sha256:fea4ec813c965e40af29ee627a1579ee1d827d77e81d54b85bdd7b42d1540cdd"},
-    {file = "ruff-0.4.0.tar.gz", hash = "sha256:7457308d9ebf00d6a1c9a26aa755e477787a636c90b823f91cd7d4bea9e89260"},
+    {file = "ruff-0.5.0-py3-none-linux_armv6l.whl", hash = "sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c"},
+    {file = "ruff-0.5.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6"},
+    {file = "ruff-0.5.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370"},
+    {file = "ruff-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3"},
+    {file = "ruff-0.5.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38"},
+    {file = "ruff-0.5.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a"},
+    {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362"},
+    {file = "ruff-0.5.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8"},
+    {file = "ruff-0.5.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d"},
+    {file = "ruff-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c"},
+    {file = "ruff-0.5.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d"},
+    {file = "ruff-0.5.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e"},
+    {file = "ruff-0.5.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf"},
+    {file = "ruff-0.5.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e"},
+    {file = "ruff-0.5.0-py3-none-win32.whl", hash = "sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c"},
+    {file = "ruff-0.5.0-py3-none-win_amd64.whl", hash = "sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440"},
+    {file = "ruff-0.5.0-py3-none-win_arm64.whl", hash = "sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178"},
+    {file = "ruff-0.5.0.tar.gz", hash = "sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1"},
 ]
 
 [[package]]
@@ -3875,4 +3885,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
 [metadata]
 lock-version = "2.0"
 python-versions = ">3.8.1,<3.12"
-content-hash = "05c80b3a3acd9ac7b59df75965e203aa21c309a8775c65027a9d20f584d15609"
+content-hash = "fcd06dc1d46838bf13549934d70debffa306fcc0973fb7586cb8a0e7dd806c6f"
diff --git a/streampipes-client-python/pyproject.toml b/streampipes-client-python/pyproject.toml
index de1547ba4b..5e26913ffb 100644
--- a/streampipes-client-python/pyproject.toml
+++ b/streampipes-client-python/pyproject.toml
@@ -72,7 +72,7 @@ flake8 = "7.1.0"
 interrogate = { version = "1.7.0", extras = ["png"] }
 isort = "5.13.0"
 mypy = "1.10.0"
-ruff = "0.4.0"
+ruff = "0.5.0"
 pre-commit = "3.5.0"
 pytest = "8.2.0"
 pytest-cov = "5.0.0"
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/SupportsNestedTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/SupportsNestedTransformationRule.java
index 86a36b7d6c..d4ee006249 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/SupportsNestedTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/SupportsNestedTransformationRule.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.connect.shared.preprocessing;
 
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 
 import java.util.List;
 import java.util.Map;
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/elements/AdapterTransformationPipelineElement.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/elements/AdapterTransformationPipelineElement.java
index 2b6d5070b2..c3d6bb2445 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/elements/AdapterTransformationPipelineElement.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/elements/AdapterTransformationPipelineElement.java
@@ -19,9 +19,9 @@
 package org.apache.streampipes.connect.shared.preprocessing.elements;
 
 import org.apache.streampipes.connect.shared.preprocessing.generator.TransformationRuleGeneratorVisitor;
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
 import org.apache.streampipes.connect.shared.preprocessing.utils.Utils;
 import org.apache.streampipes.extensions.api.connect.IAdapterPipelineElement;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 import org.apache.streampipes.model.connect.rules.TransformationRuleDescription;
 
 import java.util.List;
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/generator/TransformationRuleGeneratorVisitor.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/generator/TransformationRuleGeneratorVisitor.java
index 2b6a4edaff..87c9bb6881 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/generator/TransformationRuleGeneratorVisitor.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/generator/TransformationRuleGeneratorVisitor.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.connect.shared.preprocessing.generator;
 
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 import org.apache.streampipes.model.connect.rules.ITransformationRuleVisitor;
 
 import java.util.ArrayList;
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/AddValueTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/AddValueTransformationRule.java
index 77b8b12f20..8a31382ebe 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/AddValueTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/AddValueTransformationRule.java
@@ -19,7 +19,7 @@
 package org.apache.streampipes.connect.shared.preprocessing.transform.schema;
 
 import org.apache.streampipes.connect.shared.DatatypeUtils;
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 
 import java.util.Map;
 
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/MoveTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/MoveTransformationRule.java
index f6506f376c..b584b9eff2 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/MoveTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/MoveTransformationRule.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.connect.shared.preprocessing.transform.schema;
 
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 
 import java.util.HashMap;
 import java.util.List;
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/stream/DuplicateFilterPipelineElement.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/stream/DuplicateFilterPipelineElement.java
index bbca98fb1e..fedf106a24 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/stream/DuplicateFilterPipelineElement.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/stream/DuplicateFilterPipelineElement.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.connect.shared.preprocessing.transform.stream;
 
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 
 import java.util.HashMap;
 import java.util.Map;
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/stream/EventRateTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/stream/EventRateTransformationRule.java
index 229952c8b3..b5b586f65e 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/stream/EventRateTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/stream/EventRateTransformationRule.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.connect.shared.preprocessing.transform.stream;
 
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 
 import java.util.Map;
 
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/AddTimestampTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/AddTimestampTransformationRule.java
index c8267ef34a..f075933c33 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/AddTimestampTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/AddTimestampTransformationRule.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.connect.shared.preprocessing.transform.value;
 
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 
 import java.util.Map;
 
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/DatatypeTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/DatatypeTransformationRule.java
index ce0f0509a5..df61f2e131 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/DatatypeTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/DatatypeTransformationRule.java
@@ -20,7 +20,7 @@ package org.apache.streampipes.connect.shared.preprocessing.transform.value;
 
 
 import org.apache.streampipes.connect.shared.DatatypeUtils;
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 
 import java.util.Map;
 
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/TimestampTransformationRule.java b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/TimestampTransformationRule.java
index 5f9f30de61..05810d6072 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/TimestampTransformationRule.java
+++ b/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/TimestampTransformationRule.java
@@ -28,6 +28,8 @@ import java.text.SimpleDateFormat;
 import java.util.List;
 import java.util.Map;
 
+import static java.util.TimeZone.getTimeZone;
+
 public class TimestampTransformationRule extends SupportsNestedTransformationRule {
 
   private final List<String> eventKey;
@@ -48,6 +50,7 @@ public class TimestampTransformationRule extends SupportsNestedTransformationRul
 
     if (mode == TimestampTranformationRuleMode.FORMAT_STRING) {
       dateFormatter = new SimpleDateFormat(formatString);
+      dateFormatter.setTimeZone(getTimeZone("UTC"));
     }
   }
 
diff --git a/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/StreamPipesAdapter.java b/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/StreamPipesAdapter.java
index 4a5fb1d362..8651b05ee5 100644
--- a/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/StreamPipesAdapter.java
+++ b/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/StreamPipesAdapter.java
@@ -22,11 +22,26 @@ import org.apache.streampipes.commons.exceptions.connect.AdapterException;
 import org.apache.streampipes.extensions.api.connect.context.IAdapterGuessSchemaContext;
 import org.apache.streampipes.extensions.api.connect.context.IAdapterRuntimeContext;
 import org.apache.streampipes.extensions.api.extractor.IAdapterParameterExtractor;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
 
 public interface StreamPipesAdapter {
   IAdapterConfiguration declareConfig();
 
+  /**
+   * Preprocesses the adapter description before the adapter is invoked.
+   *
+   * <p>This method is designed to allow adapters to modify the adapter description prior to invocation.
+   * It is particularly useful for adapters that need to manipulate certain values internally,
+   * e.g. bypassing the adapter preprocessing pipeline. An example of such an adapter is the FileReplayAdapter,
+   * which manipulates timestamp values.</p>
+   *
+   * <p>This is a default method and does not need to be overridden unless specific preprocessing is required.</p>
+   *
+   * @param adapterDescription The adapter description to be preprocessed.
+   */
+  default void preprocessAdapterDescription(AdapterDescription adapterDescription) {};
+
   void onAdapterStarted(IAdapterParameterExtractor extractor,
                         IEventCollector collector,
                         IAdapterRuntimeContext adapterRuntimeContext) throws AdapterException;
diff --git a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/TransformationRule.java b/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/TransformationRule.java
similarity index 92%
rename from streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/TransformationRule.java
rename to streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/TransformationRule.java
index f6443afc62..91b877825d 100644
--- a/streampipes-connect-shared/src/main/java/org/apache/streampipes/connect/shared/preprocessing/transform/TransformationRule.java
+++ b/streampipes-extensions-api/src/main/java/org/apache/streampipes/extensions/api/connect/TransformationRule.java
@@ -16,7 +16,7 @@
  *
  */
 
-package org.apache.streampipes.connect.shared.preprocessing.transform;
+package org.apache.streampipes.extensions.api.connect;
 
 import java.util.Map;
 
diff --git a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerManagement.java b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerManagement.java
index 958d413f1f..0d9cfd31f2 100644
--- a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerManagement.java
+++ b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/connect/AdapterWorkerManagement.java
@@ -63,6 +63,11 @@ public class AdapterWorkerManagement {
           newAdapterInstance,
           adapterDescription);
 
+      // This method allows adapters to modify the adapter description prior to invocation.
+      // It is particularly useful for adapters like FileReplayAdapter that need to manipulate timestamp values
+      // internally, bypassing the adapter preprocessing pipeline.
+      newAdapterInstance.preprocessAdapterDescription(adapterDescription);
+
       var registeredParsers = newAdapterInstance.declareConfig().getSupportedParsers();
       var extractor = AdapterParameterExtractor.from(adapterDescription, registeredParsers);
       var eventCollector = EventCollector.from(adapterDescription);
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/protocol/stream/FileReplayAdapter.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/protocol/stream/FileReplayAdapter.java
index 6ca6217553..ed0d256605 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/protocol/stream/FileReplayAdapter.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/protocol/stream/FileReplayAdapter.java
@@ -20,8 +20,10 @@ package org.apache.streampipes.connect.iiot.protocol.stream;
 
 import org.apache.streampipes.commons.exceptions.connect.AdapterException;
 import org.apache.streampipes.connect.iiot.utils.FileProtocolUtils;
+import org.apache.streampipes.connect.shared.preprocessing.generator.StatelessTransformationRuleGeneratorVisitor;
 import org.apache.streampipes.extensions.api.connect.IAdapterConfiguration;
 import org.apache.streampipes.extensions.api.connect.IEventCollector;
+import org.apache.streampipes.extensions.api.connect.IParser;
 import org.apache.streampipes.extensions.api.connect.StreamPipesAdapter;
 import org.apache.streampipes.extensions.api.connect.context.IAdapterGuessSchemaContext;
 import org.apache.streampipes.extensions.api.connect.context.IAdapterRuntimeContext;
@@ -31,8 +33,11 @@ import org.apache.streampipes.extensions.management.connect.adapter.parser.Image
 import org.apache.streampipes.extensions.management.connect.adapter.parser.JsonParsers;
 import org.apache.streampipes.extensions.management.connect.adapter.parser.xml.XmlParser;
 import org.apache.streampipes.model.AdapterType;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
 import org.apache.streampipes.model.connect.rules.schema.RenameRuleDescription;
+import org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription;
+import org.apache.streampipes.model.connect.rules.value.TimestampTranfsformationRuleDescription;
 import org.apache.streampipes.model.extensions.ExtensionAssetType;
 import org.apache.streampipes.sdk.StaticProperties;
 import org.apache.streampipes.sdk.builder.adapter.AdapterConfigurationBuilder;
@@ -50,6 +55,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -76,6 +82,8 @@ public class FileReplayAdapter implements StreamPipesAdapter {
   private boolean replaceTimestamp;
   private String timestampRuntimeName;
 
+  private TimestampTranfsformationRuleDescription timestampTranfsformationRuleDescription;
+
   private String timestampSourceFieldName;
 
   private float speedUp;
@@ -84,7 +92,8 @@ public class FileReplayAdapter implements StreamPipesAdapter {
 
   @Override
   public IAdapterConfiguration declareConfig() {
-    return AdapterConfigurationBuilder.create(ID, 0, FileReplayAdapter::new)
+    return AdapterConfigurationBuilder
+        .create(ID, 0, FileReplayAdapter::new)
         .withSupportedParsers(
             new JsonParsers(),
             new CsvParser(),
@@ -105,7 +114,8 @@ public class FileReplayAdapter implements StreamPipesAdapter {
             Labels.withId(REPLAY_ONCE),
             Options.from("no", "yes")
         )
-        .requiredAlternatives(Labels.withId(SPEED),
+        .requiredAlternatives(
+            Labels.withId(SPEED),
             Alternatives.from(Labels.withId(KEEP_ORIGINAL_TIME), true),
             Alternatives.from(Labels.withId(FASTEST)),
             Alternatives.from(
@@ -127,8 +137,54 @@ public class FileReplayAdapter implements StreamPipesAdapter {
       IAdapterRuntimeContext adapterRuntimeContext
   ) throws AdapterException {
 
-    // extract user input
+    throwExceptionWhenAddTimestampRuleIsSelected(extractor);
+
+    boolean replayOnce = extractUserInputsAndReturnValueOfReplayOnce(extractor);
+
+    determineTimestampRuntimeName(extractor);
+
+    determineSourceTimestampField(extractor);
+
+    startAdapterReplayThread(extractor, collector, adapterRuntimeContext, replayOnce);
+  }
+
+  protected static void throwExceptionWhenAddTimestampRuleIsSelected(IAdapterParameterExtractor extractor)
+      throws AdapterException {
+    boolean ruleExists = extractor.getAdapterDescription()
+                                  .getRules()
+                                  .stream()
+                                  .anyMatch(rule -> rule instanceof AddTimestampRuleDescription);
+
+    if (ruleExists) {
+      throw new AdapterException("The file replay adapter requires a valid timestamp within the file. The add "
+                                     + "timestamp option in the schema editor is not supported. Please edit the "
+                                     + "adater to resolve this problem.");
+    }
+  }
+
+  private void startAdapterReplayThread(
+      IAdapterParameterExtractor extractor,
+      IEventCollector collector,
+      IAdapterRuntimeContext adapterRuntimeContext,
+      boolean replayOnce
+  ) {
     executor = Executors.newScheduledThreadPool(1);
+    if (replayOnce) {
+      executor.schedule(
+          () -> getFileFromEndpointAndParseFile(extractor, collector, adapterRuntimeContext),
+          0,
+          TimeUnit.SECONDS
+      );
+    } else {
+      executor.scheduleAtFixedRate(
+          () -> getFileFromEndpointAndParseFile(extractor, collector, adapterRuntimeContext),
+          0,
+          1,
+          TimeUnit.SECONDS);
+    }
+  }
+
+  private boolean extractUserInputsAndReturnValueOfReplayOnce(IAdapterParameterExtractor extractor) {
     boolean replayOnce = extractor
         .getStaticPropertyExtractor()
         .selectedSingleValue(REPLAY_ONCE, String.class)
@@ -147,41 +203,25 @@ public class FileReplayAdapter implements StreamPipesAdapter {
           .singleValueParameter(SPEED_UP, Float.class);
       default -> 1.0f;
     };
+    return replayOnce;
+  }
 
-    // get timestamp field
+  private void determineTimestampRuntimeName(IAdapterParameterExtractor extractor) throws AdapterException {
     var timestampField = extractor
         .getAdapterDescription()
         .getEventSchema()
         .getEventProperties()
         .stream()
         .filter(eventProperty ->
-            eventProperty.getDomainProperties()
-                .contains(URI.create(SO.DATE_TIME)))
+                    eventProperty.getDomainProperties()
+                                 .contains(URI.create(SO.DATE_TIME)))
         .findFirst();
 
     if (timestampField.isEmpty()) {
       throw new AdapterException("Could not find a timestamp field in event schema");
     } else {
       timestampRuntimeName = timestampField.get()
-          .getRuntimeName();
-    }
-
-    determineSourceTimestampField(extractor);
-
-    // start replay adapter
-    if (replayOnce) {
-      executor.schedule(
-          () -> parseFile(extractor, collector, adapterRuntimeContext),
-          0,
-          TimeUnit.SECONDS
-      );
-    } else {
-      executor.scheduleAtFixedRate(
-          () -> parseFile(extractor, collector, adapterRuntimeContext),
-          0,
-          1,
-          TimeUnit.SECONDS
-      );
+                                           .getRuntimeName();
     }
   }
 
@@ -199,10 +239,10 @@ public class FileReplayAdapter implements StreamPipesAdapter {
     if (!renamingRulesTimestamp.isEmpty()) {
       if (renamingRulesTimestamp.size() == 1) {
         timestampSourceFieldName = renamingRulesTimestamp.get(0)
-            .getOldRuntimeKey();
+                                                         .getOldRuntimeKey();
       } else {
         throw new AdapterException("Invalid configuration - multiple renaming rules detected which affect the "
-            + "timestamp property.");
+                                       + "timestamp property.");
       }
     } else {
       // no renaming rules can be found, timestamp name does not change
@@ -219,73 +259,32 @@ public class FileReplayAdapter implements StreamPipesAdapter {
   private List<RenameRuleDescription> getRenamingRulesTimestamp(IAdapterParameterExtractor extractor) {
     var renamingRules =
         extractor.getAdapterDescription()
-            .getRules()
-            .stream()
-            .filter(rule -> rule instanceof RenameRuleDescription)
-            .map(rule -> (RenameRuleDescription) rule)
-            .toList();
+                 .getRules()
+                 .stream()
+                 .filter(rule -> rule instanceof RenameRuleDescription)
+                 .map(rule -> (RenameRuleDescription) rule)
+                 .toList();
 
     return renamingRules.stream()
-        .filter(rule -> rule.getNewRuntimeKey()
-            .equals(timestampRuntimeName))
-        .toList();
+                        .filter(rule -> rule.getNewRuntimeKey()
+                                            .equals(timestampRuntimeName))
+                        .toList();
   }
 
-  private void parseFile(
+  private void getFileFromEndpointAndParseFile(
       IAdapterParameterExtractor extractor,
       IEventCollector collector,
       IAdapterRuntimeContext adapterRuntimeContext
   ) {
     try {
-      InputStream inputStream = getDataFromEndpoint(extractor
-          .getStaticPropertyExtractor()
-          .selectedFilename(FILE_PATH));
-
-      extractor.selectedParser()
-          .parse(inputStream, (event) -> {
-
-            long actualEventTimestamp = -1;
-            var timestampFieldValue = event.get(timestampSourceFieldName);
-            if (timestampFieldValue instanceof Long) {
-              actualEventTimestamp = (Long) timestampFieldValue;
-            } else if (timestampFieldValue instanceof Integer) {
-              actualEventTimestamp = (Integer) timestampFieldValue;
-            } else if (!(timestampFieldValue == null && replaceTimestamp)){
-              adapterRuntimeContext
-                  .getLogger()
-                  .error(
-                      new AdapterException("Timestamp field is not a unix timestamp in ms, skipping event. Value: %s"
-                          .formatted(event.get(timestampSourceFieldName))
-                      ));
-            }
-
-            if (actualEventTimestamp == -1 && !replaceTimestamp) {
-              // Do not emit any data if timestamp could not be processed
-              return;
-            }
-
-            long sleepTime;
-            if (timestampLastEvent != -1 && actualEventTimestamp != -1) {
-              sleepTime = (long) ((actualEventTimestamp - timestampLastEvent) / speedUp);
-            } else {
-              sleepTime = 1;
-            }
-            // speed up is set to Float.MAX_VALUE when user selected fastest option
-            if (sleepTime > 0 && speedUp != Float.MAX_VALUE) {
-              try {
-                Thread.sleep(sleepTime);
-              } catch (InterruptedException e) {
-                LOG.info("File stream adapter was stopped, the current replay is interrupted", e);
-              }
-            }
-
-            timestampLastEvent = actualEventTimestamp;
-            if (replaceTimestamp) {
-              event.put(timestampSourceFieldName, System.currentTimeMillis());
-            }
-
-            collector.collect(event);
-          });
+      var inputStream = getFileAsInputStreamFromEndpoint(extractor);
+
+      parseFile(
+          extractor.selectedParser(),
+          collector,
+          inputStream,
+          adapterRuntimeContext
+      );
 
     } catch (AdapterException e) {
       adapterRuntimeContext
@@ -294,6 +293,103 @@ public class FileReplayAdapter implements StreamPipesAdapter {
     }
   }
 
+  private void parseFile(
+      IParser parser,
+      IEventCollector collector,
+      InputStream inputStream,
+      IAdapterRuntimeContext adapterRuntimeContext
+  ) {
+    // The parse method does not throw AdapterExceptions, that's why the logging is handeled within the catch blog here
+    parser.parse(inputStream, (event) -> {
+      try {
+        processEvent(collector, event);
+      } catch (AdapterException e) {
+        adapterRuntimeContext
+            .getLogger()
+            .error(e);
+      }
+    });
+  }
+
+  protected void processEvent(
+      IEventCollector collector,
+      Map<String, Object> event
+  ) throws AdapterException {
+
+    long actualEventTimestamp = getTimestampFromEvent(event);
+
+    reduceReplaySpeedIfRequired(actualEventTimestamp);
+
+    // This must be the last step, because the original timestamp must be used to simulate the replay frequency
+    // of the original file
+    replaceTimestampIfRequired(event);
+
+    timestampLastEvent = actualEventTimestamp;
+    collector.collect(event);
+  }
+
+  protected long getTimestampFromEvent(Map<String, Object> event) throws AdapterException {
+    long actualEventTimestamp = -1;
+
+    var timestampFieldValue = event.get(timestampSourceFieldName);
+
+    if (timestampFieldValue instanceof Long) {
+      actualEventTimestamp = (Long) timestampFieldValue;
+    } else if (timestampFieldValue instanceof Integer) {
+      actualEventTimestamp = (Integer) timestampFieldValue;
+    }
+
+    // transform timestamp if transformation rule is present
+    actualEventTimestamp = transformTimestampIfTransformationRuleIsPresent(event, actualEventTimestamp);
+
+
+    if (actualEventTimestamp == -1 && !replaceTimestamp) {
+      throw new AdapterException("Timestamp field could not be parsed, skipping event. "
+                                     + "Value: %s".formatted(event.get(timestampSourceFieldName)));
+    }
+
+    return actualEventTimestamp;
+  }
+
+  private long transformTimestampIfTransformationRuleIsPresent(Map<String, Object> event, long actualEventTimestamp) {
+    if (timestampTranfsformationRuleDescription != null) {
+      var transformationRuleDescription = timestampTranfsformationRuleDescription;
+
+      var transformationRuleVisitor = new StatelessTransformationRuleGeneratorVisitor();
+      transformationRuleVisitor.visit(transformationRuleDescription);
+      var timestampTransformationRule = transformationRuleVisitor.getTransformationRules()
+                                                                 .get(0);
+
+      actualEventTimestamp = (Long) (
+          timestampTransformationRule.apply(event)
+                                     .get(timestampSourceFieldName));
+    }
+    return actualEventTimestamp;
+  }
+
+  private void reduceReplaySpeedIfRequired(long actualEventTimestamp) {
+    long sleepTime;
+    if (timestampLastEvent != -1 && actualEventTimestamp != -1) {
+      sleepTime = (long) ((actualEventTimestamp - timestampLastEvent) / speedUp);
+    } else {
+      sleepTime = 1;
+    }
+    // speed up is set to Float.MAX_VALUE when user selected fastest option
+    if (sleepTime > 0 && speedUp != Float.MAX_VALUE) {
+      try {
+        Thread.sleep(sleepTime);
+      } catch (InterruptedException e) {
+        LOG.info("File stream adapter was stopped, the current replay is interrupted", e);
+      }
+    }
+  }
+
+  private void replaceTimestampIfRequired(Map<String, Object> event) {
+    if (replaceTimestamp) {
+      event.put(timestampSourceFieldName, System.currentTimeMillis());
+    }
+  }
+
   @Override
   public void onAdapterStopped(IAdapterParameterExtractor extractor, IAdapterRuntimeContext adapterRuntimeContext) {
     executor.shutdownNow();
@@ -301,19 +397,63 @@ public class FileReplayAdapter implements StreamPipesAdapter {
   }
 
   @Override
-  public GuessSchema onSchemaRequested(IAdapterParameterExtractor extractor,
-                                       IAdapterGuessSchemaContext adapterGuessSchemaContext) throws AdapterException {
-    var inputStream = getDataFromEndpoint(extractor
-        .getStaticPropertyExtractor()
-        .selectedFilename(FILE_PATH));
-    return extractor.selectedParser().getGuessSchema(inputStream);
+  public GuessSchema onSchemaRequested(
+      IAdapterParameterExtractor extractor,
+      IAdapterGuessSchemaContext adapterGuessSchemaContext
+  ) throws AdapterException {
+    var inputStream = getFileAsInputStreamFromEndpoint(extractor);
+    return extractor.selectedParser()
+                    .getGuessSchema(inputStream);
   }
 
-  private InputStream getDataFromEndpoint(String selectedFileName) throws AdapterException {
+  private InputStream getFileAsInputStreamFromEndpoint(IAdapterParameterExtractor extractor) throws AdapterException {
+    var selectedFileName = extractor
+        .getStaticPropertyExtractor()
+        .selectedFilename(FILE_PATH);
+
     try {
       return FileProtocolUtils.getFileInputStream(selectedFileName);
     } catch (IOException e) {
       throw new AdapterException("Could not find file: " + selectedFileName, e);
     }
   }
+
+  protected void setTimestampSourceFieldName(String timestampSourceFieldName) {
+    this.timestampSourceFieldName = timestampSourceFieldName;
+  }
+
+  protected void setReplaceTimestamp(boolean replaceTimestamp) {
+    this.replaceTimestamp = replaceTimestamp;
+  }
+
+  protected void setTimestampTranfsformationRuleDescription(
+      TimestampTranfsformationRuleDescription timestampTranfsformationRuleDescription
+  ) {
+    this.timestampTranfsformationRuleDescription = timestampTranfsformationRuleDescription;
+  }
+
+  /**
+   * Removes the timestamp transformation rules from the adapter description.
+   *
+   * <p>The FileReplay adapter manages timestamp transformations internally to accurately simulate the replay frequency.
+   * This is necessary as the timestamp field values are crucial for this simulation. As a result, the timestamp rule
+   * description is stored locally within the FileReplay adapter and is applied when the onAdapterStarted method is
+   * invoked.</p>
+   */
+  @Override
+  public void preprocessAdapterDescription(AdapterDescription adapterDescription) {
+
+    this.timestampTranfsformationRuleDescription = adapterDescription
+        .getRules()
+        .stream()
+        .filter(rule -> rule instanceof TimestampTranfsformationRuleDescription)
+        .map(rule -> (TimestampTranfsformationRuleDescription) rule)
+        .findFirst()
+        .orElse(null);
+
+    // remove timestamp preprocessing rule
+    adapterDescription
+        .getRules()
+        .removeIf(rule -> rule instanceof TimestampTranfsformationRuleDescription);
+  }
 }
diff --git a/ui/cypress/fixtures/connect/fileReplay/noTimestamp.csv b/ui/cypress/fixtures/connect/fileReplay/noTimestamp.csv
new file mode 100644
index 0000000000..6f3e084fc7
--- /dev/null
+++ b/ui/cypress/fixtures/connect/fileReplay/noTimestamp.csv
@@ -0,0 +1,2 @@
+count;randomnumber;randomtext
+122.0;62.0;c
diff --git a/ui/cypress/fixtures/connect/fileReplay/timestampInMilliseconds/expected.csv b/ui/cypress/fixtures/connect/fileReplay/timestampInMilliseconds/expected.csv
new file mode 100644
index 0000000000..b19de0291b
--- /dev/null
+++ b/ui/cypress/fixtures/connect/fileReplay/timestampInMilliseconds/expected.csv
@@ -0,0 +1,4 @@
+timestamp;count
+1623871499111;122.0
+1623871500222;123.0
+1623871508333;131.0
diff --git a/ui/cypress/fixtures/connect/fileReplay/timestampInMilliseconds/input.csv b/ui/cypress/fixtures/connect/fileReplay/timestampInMilliseconds/input.csv
new file mode 100644
index 0000000000..b19de0291b
--- /dev/null
+++ b/ui/cypress/fixtures/connect/fileReplay/timestampInMilliseconds/input.csv
@@ -0,0 +1,4 @@
+timestamp;count
+1623871499111;122.0
+1623871500222;123.0
+1623871508333;131.0
diff --git a/ui/cypress/fixtures/connect/fileReplay/timestampInSeconds/expected.csv b/ui/cypress/fixtures/connect/fileReplay/timestampInSeconds/expected.csv
new file mode 100644
index 0000000000..bba7da1ee1
--- /dev/null
+++ b/ui/cypress/fixtures/connect/fileReplay/timestampInSeconds/expected.csv
@@ -0,0 +1,4 @@
+timestamp;count
+1623871499000;122.0
+1623871500000;123.0
+1623871508000;131.0
diff --git a/ui/cypress/fixtures/connect/fileReplay/timestampInSeconds/input.csv b/ui/cypress/fixtures/connect/fileReplay/timestampInSeconds/input.csv
new file mode 100644
index 0000000000..af50b6c07c
--- /dev/null
+++ b/ui/cypress/fixtures/connect/fileReplay/timestampInSeconds/input.csv
@@ -0,0 +1,4 @@
+timestamp;count
+1623871499;122.0
+1623871500;123.0
+1623871508;131.0
diff --git a/ui/cypress/fixtures/connect/schemaRules/input.csv b/ui/cypress/fixtures/connect/schemaRules/input.csv
index f92f0b8ceb..be4e9eff3b 100644
--- a/ui/cypress/fixtures/connect/schemaRules/input.csv
+++ b/ui/cypress/fixtures/connect/schemaRules/input.csv
@@ -1,2 +1,2 @@
-count;density;temperature
-122.0;62.0;11
+timestamp;count;density;temperature
+1720018277000;122.0;62.0;11
diff --git a/ui/cypress/support/builder/AdapterBuilder.ts b/ui/cypress/support/builder/AdapterBuilder.ts
index c8c029c599..d590d95005 100644
--- a/ui/cypress/support/builder/AdapterBuilder.ts
+++ b/ui/cypress/support/builder/AdapterBuilder.ts
@@ -41,6 +41,12 @@ export class AdapterBuilder {
 
     public setTimestampProperty(timestsmpProperty: string) {
         this.adapterInput.timestampProperty = timestsmpProperty;
+        this.adapterInput.autoAddTimestamp;
+        return this;
+    }
+
+    public setAutoAddTimestampPropery() {
+        this.adapterInput.autoAddTimestamp = true;
         return this;
     }
 
diff --git a/ui/cypress/support/utils/connect/ConnectBtns.ts b/ui/cypress/support/utils/connect/ConnectBtns.ts
index f828bcf148..f31db5f0e8 100644
--- a/ui/cypress/support/utils/connect/ConnectBtns.ts
+++ b/ui/cypress/support/utils/connect/ConnectBtns.ts
@@ -71,6 +71,48 @@ export class ConnectBtns {
 
     // ========================================================================
 
+    // =====================  Event Schema buttons  ==========================
+
+    public static schemaUnitFromDropdown() {
+        return cy.dataCy('connect-schema-unit-from-dropdown');
+    }
+
+    public static schemaUnitTransformBtn() {
+        return cy.dataCy('connect-schema-unit-transform-btn');
+    }
+
+    public static schemaUnitToDropdown() {
+        return cy.dataCy('connect-schema-unit-to-dropdown');
+    }
+
+    public static saveEditProperty() {
+        return cy.dataCy('sp-save-edit-property', { timeout: 10000 });
+    }
+
+    public static markAsTimestampBtn() {
+        return cy.dataCy('sp-mark-as-timestamp').children();
+    }
+
+    public static setTimestampConverter(option: 'Number' | 'String') {
+        cy.dataCy('connect-timestamp-converter')
+            .click()
+            .get('mat-option')
+            .contains(option)
+            .click();
+    }
+
+    public static timestampStringRegex() {
+        return cy.dataCy('connect-timestamp-string-regex', { timeout: 10000 });
+    }
+
+    public static timestampNumberDropdown() {
+        return cy.dataCy('connect-timestamp-number-dropdown', {
+            timeout: 10000,
+        });
+    }
+
+    // ========================================================================
+
     // =====================  Format configurations  ==========================
 
     public static csvDelimiter() {
diff --git a/ui/cypress/support/utils/connect/ConnectEventSchemaUtils.ts b/ui/cypress/support/utils/connect/ConnectEventSchemaUtils.ts
index 098ea68554..1e302003ec 100644
--- a/ui/cypress/support/utils/connect/ConnectEventSchemaUtils.ts
+++ b/ui/cypress/support/utils/connect/ConnectEventSchemaUtils.ts
@@ -35,7 +35,7 @@ export class ConnectEventSchemaUtils {
         ConnectEventSchemaUtils.clickEditProperty(propertyName);
 
         // Mark as timestamp
-        cy.dataCy('sp-mark-as-timestamp').children().click();
+        ConnectBtns.markAsTimestampBtn().click();
 
         // Close
         cy.dataCy('sp-save-edit-property').click();
@@ -51,33 +51,55 @@ export class ConnectEventSchemaUtils {
         this.eventSchemaNextBtnEnabled();
     }
 
-    public static editTimestampProperty(
+    public static editTimestampPropertyWithRegex(
         propertyName: string,
         timestampRegex: string,
     ) {
         ConnectEventSchemaUtils.clickEditProperty(propertyName);
-        cy.dataCy('sp-mark-as-timestamp').children().click();
-        cy.dataCy('connect-timestamp-converter')
-            .click()
-            .get('mat-option')
-            .contains('String')
-            .click();
-        cy.dataCy('connect-timestamp-string-regex').type(timestampRegex);
 
-        cy.dataCy('sp-save-edit-property').click();
+        ConnectBtns.markAsTimestampBtn().click();
+        ConnectBtns.setTimestampConverter('String');
+
+        ConnectBtns.timestampStringRegex().type(timestampRegex);
+
+        ConnectBtns.saveEditProperty().click();
 
+        // The following code validates that the regex is persisted by reopening the edit dialog again
         cy.dataCy('edit-' + propertyName.toLowerCase(), {
             timeout: 10000,
         }).click({ force: true });
-        cy.dataCy('connect-timestamp-string-regex', { timeout: 10000 }).should(
-            'have.value',
-            timestampRegex,
-        );
-        cy.dataCy('sp-save-edit-property', { timeout: 10000 }).should(
-            'have.length',
-            1,
+        ConnectBtns.timestampStringRegex().should('have.value', timestampRegex);
+
+        ConnectBtns.saveEditProperty().should('have.length', 1);
+        ConnectBtns.saveEditProperty().click();
+    }
+
+    public static editTimestampPropertyWithNumber(
+        propertyName: string,
+        configurationValue: 'Seconds' | 'Milliseconds',
+    ) {
+        ConnectEventSchemaUtils.clickEditProperty(propertyName);
+        ConnectBtns.markAsTimestampBtn().click();
+
+        ConnectBtns.setTimestampConverter('Number');
+
+        ConnectBtns.timestampNumberDropdown()
+            .click({ force: true })
+            .get('mat-option')
+            .contains(configurationValue)
+            .click();
+
+        ConnectBtns.saveEditProperty().click();
+
+        // Check if the configuration is persisted by reopening the edit dialog
+        ConnectEventSchemaUtils.clickEditProperty(propertyName);
+
+        ConnectBtns.timestampNumberDropdown().should(
+            'contain',
+            configurationValue,
         );
-        cy.dataCy('sp-save-edit-property').click();
+
+        ConnectBtns.saveEditProperty().click();
     }
 
     public static numberTransformation(propertyName: string, value: string) {
@@ -111,37 +133,15 @@ export class ConnectEventSchemaUtils {
         toUnit: string,
     ) {
         ConnectEventSchemaUtils.clickEditProperty(propertyName);
-        cy.dataCy('connect-schema-unit-from-dropdown').type(fromUnit);
-        cy.dataCy('connect-schema-unit-transform-btn').click();
-        cy.dataCy('connect-schema-unit-to-dropdown')
-            .click()
+        ConnectBtns.schemaUnitFromDropdown().type(fromUnit);
+        ConnectBtns.schemaUnitTransformBtn().click();
+        ConnectBtns.schemaUnitToDropdown().click();
+
+        ConnectBtns.schemaUnitToDropdown()
             .get('mat-option')
             .contains(toUnit)
             .click();
-        cy.dataCy('sp-save-edit-property').click();
-
-        cy.dataCy('edit-' + propertyName.toLowerCase(), {
-            timeout: 10000,
-        }).click({ force: true });
-
-        cy.dataCy('connect-schema-unit-from-dropdown').eq(0).clear();
-        cy.dataCy('connect-schema-unit-from-dropdown').eq(0).type(fromUnit);
-        cy.dataCy('connect-schema-unit-from-dropdown', {
-            timeout: 10000,
-        }).should('have.value', fromUnit);
-        //cy.dataCy("connect-schema-unit-transform-btn").click();
-        cy.dataCy('connect-schema-unit-to-dropdown')
-            .contains(toUnit)
-            .click({ force: true });
-        cy.dataCy('connect-schema-unit-to-dropdown', {
-            timeout: 10000,
-        }).contains(toUnit);
-
-        cy.dataCy('sp-save-edit-property', { timeout: 10000 }).should(
-            'have.length',
-            1,
-        );
-        cy.dataCy('sp-save-edit-property').click({ force: true });
+        ConnectBtns.saveEditProperty().click();
     }
 
     public static addStaticProperty(
diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts b/ui/cypress/support/utils/connect/ConnectUtils.ts
index fa30cd01e0..056ab9d2a9 100644
--- a/ui/cypress/support/utils/connect/ConnectUtils.ts
+++ b/ui/cypress/support/utils/connect/ConnectUtils.ts
@@ -26,7 +26,10 @@ import { UserUtils } from '../UserUtils';
 import { PipelineUtils } from '../PipelineUtils';
 
 export class ConnectUtils {
-    public static testAdapter(adapterConfiguration: AdapterInput) {
+    public static testAdapter(
+        adapterConfiguration: AdapterInput,
+        adapterStartFails = false,
+    ) {
         ConnectUtils.goToConnect();
 
         ConnectUtils.goToNewAdapterPage();
@@ -47,7 +50,11 @@ export class ConnectUtils {
 
         ConnectEventSchemaUtils.finishEventSchemaConfiguration();
 
-        ConnectUtils.startStreamAdapter(adapterConfiguration);
+        ConnectUtils.startAdapter(
+            adapterConfiguration,
+            false,
+            adapterStartFails,
+        );
     }
 
     public static addAdapter(adapterConfiguration: AdapterInput) {
@@ -100,7 +107,7 @@ export class ConnectUtils {
 
         ConnectEventSchemaUtils.finishEventSchemaConfiguration();
 
-        ConnectUtils.startStreamAdapter(configuration);
+        ConnectUtils.startAdapter(configuration);
     }
 
     public static goToConnect() {
@@ -148,13 +155,10 @@ export class ConnectUtils {
         cy.get('#event-schema-next-button').click();
     }
 
-    public static startStreamAdapter(adapterInput: AdapterInput) {
-        ConnectUtils.startAdapter(adapterInput);
-    }
-
     public static startAdapter(
         adapterInput: AdapterInput,
         noLiveDataView = false,
+        adapterStartFails = false,
     ) {
         // Set adapter name
         cy.dataCy('sp-adapter-name').type(adapterInput.adapterName);
@@ -175,14 +179,20 @@ export class ConnectUtils {
 
         ConnectBtns.adapterSettingsStartAdapter().click();
 
-        if (adapterInput.startAdapter && !noLiveDataView) {
-            cy.dataCy('sp-connect-adapter-success-live-preview', {
+        if (adapterStartFails) {
+            cy.dataCy('sp-connect-adapter-error-message', {
                 timeout: 60000,
             }).should('be.visible');
         } else {
-            cy.dataCy('sp-connect-adapter-success-added', {
-                timeout: 60000,
-            }).should('be.visible');
+            if (adapterInput.startAdapter && !noLiveDataView) {
+                cy.dataCy('sp-connect-adapter-success-live-preview', {
+                    timeout: 60000,
+                }).should('be.visible');
+            } else {
+                cy.dataCy('sp-connect-adapter-success-added', {
+                    timeout: 60000,
+                }).should('be.visible');
+            }
         }
 
         this.closeAdapterPreview();
diff --git a/ui/deployment/home.service.mst b/ui/deployment/home.service.mst
index f22b25a772..9a6a6de7a9 100644
--- a/ui/deployment/home.service.mst
+++ b/ui/deployment/home.service.mst
@@ -19,42 +19,66 @@
 import { Injectable } from '@angular/core';
 import { PageName } from '../_enums/page-name.enum';
 import { AuthService } from '../services/auth.service';
+import { ServiceLink } from './models/home.model';
+import {
+    AdapterService,
+    DashboardService,
+    DataViewDataExplorerService, PipelineElementService,
+    PipelineService
+} from '@streampipes/platform-services';
+import { UserRole } from '../_enums/user-role.enum';
 
 @Injectable({providedIn: 'root'})
 export class HomeService {
 
-    constructor(private authService: AuthService) {
+    constructor(private authService: AuthService,
+                private adapterService: AdapterService,
+                private dashboardService: DashboardService,
+                private dataViewService: DataViewDataExplorerService,
+                private pipelineService: PipelineService,
+                private pipelineElementService: PipelineElementService) {
     }
 
     getFilteredServiceLinks() {
         return this.getServiceLinks().filter(s => this.authService.isAnyAccessGranted(s.pageNames));
     }
 
-    getServiceLinks() {
+    getServiceLinks(): ServiceLink[] {
         return [
            {{#modulesActive}}
            {
                name: "{{title}}",
                description: "{{description}}",
-               imageUrl: "{{{homeImage}}}",
                icon: "{{{icon}}}",
                pageNames: [{{{pageNames}}}],
                link: {
                    newWindow: false,
                    value: "{{{link}}}"
+               },
+               showStatusBox: {{{showStatusBox}}}{{#statusBox}},
+               statusBox: {
+                    link: {{{statusBox.link}}},
+                    createLink: {{{statusBox.createLinks}}},
+                    createTitle: '{{{statusBox.createTitle}}}',
+                    title: '{{{statusBox.title}}}',
+                    dataFns: {{{statusBox.dataFns}}},
+                    viewRoles: {{{statusBox.viewRoles}}},
+                    createRoles: {{{statusBox.createRoles}}},
+                    icon: '{{{icon}}}'
                }
+               {{/statusBox}}
            },
            {{/modulesActive}}
            {
                name: "Notifications",
                description: "The notification module lets you view all notifications generated by pipelines.",
-               imageUrl: "assets/img/home/notifications.png",
                icon: "chat",
                pageNames: [PageName.NOTIFICATIONS],
                link: {
                    newWindow: false,
                    value: "notifications"
-               }
+               },
+               showStatusBox: false
            }
        ];
     }
diff --git a/ui/deployment/modules.yml b/ui/deployment/modules.yml
index 0250c7c8d6..364d8aa77d 100644
--- a/ui/deployment/modules.yml
+++ b/ui/deployment/modules.yml
@@ -24,23 +24,9 @@ spAssets:
     title: 'Asset Management'
     description: 'Manage assets which help assigning pipelines, dashboards and adapters to real-world objects such as machines and plants.'
     icon: 'precision_manufacturing'
-    homeImage: ''
     admin: true
     pageNames: 'PageName.ASSETS'
-spEditor:
-    componentImport: True
-    ng5_moduleName: 'EditorModule'
-    ng5_component: 'EditorComponent'
-    ng5_componentPath: './editor/editor.component'
-    path: './editor/editor.module'
-    link: 'editor'
-    url: '/editor/:pipeline'
-    title: 'Pipeline Editor'
-    description: 'The pipeline editor lets you create flexible data pipelines based on installed data streams, data processors, and data sinks.'
-    icon: 'dashboard'
-    homeImage: '/assets/img/home/editor.png'
-    admin: false
-    pageNames: 'PageName.PIPELINE_EDITOR'
+    showStatusBox: false
 spConnect:
     componentImport: False
     ng5_moduleName: 'ConnectModule'
@@ -52,9 +38,17 @@ spConnect:
     title: 'Connect'
     description: 'StreamPipes Connect lets you easily connect new data sources for a variety of data formats and protocols.'
     icon: 'power'
-    homeImage: '/assets/img/home/editor.png'
     admin: False
     pageNames: 'PageName.CONNECT'
+    showStatusBox: true
+    statusBox:
+        link: "['connect']"
+        createLinks: "['connect', 'create']"
+        title: 'Adapters'
+        createTitle: 'New adapter'
+        dataFns: '[this.adapterService.getAdapters()]'
+        viewRoles: '[UserRole.ROLE_CONNECT_ADMIN]'
+        createRoles: '[UserRole.ROLE_CONNECT_ADMIN]'
 spPipelines:
     componentImport: False
     ng5_moduleName: 'PipelinesModule'
@@ -66,9 +60,17 @@ spPipelines:
     title: 'Pipelines'
     description: 'All created pipelines are available in the pipeline view, where you can also start and stop pipelines.'
     icon: 'play_arrow'
-    homeImage: '/assets/img/home/pipelines.png'
     admin: False
     pageNames: 'PageName.PIPELINE_OVERVIEW'
+    showStatusBox: true
+    statusBox:
+        link: "['pipelines']"
+        createLinks: "['pipelines', 'create']"
+        title: 'Pipelines'
+        createTitle: 'New pipeline'
+        dataFns: '[this.pipelineService.getPipelines()]'
+        viewRoles: '[UserRole.ROLE_PIPELINE_ADMIN, UserRole.ROLE_PIPELINE_USER]'
+        createRoles: '[UserRole.ROLE_PIPELINE_ADMIN]'
 spConfiguration:
     componentImport: False
     ng5_moduleName: 'ConfigurationModule'
@@ -80,9 +82,9 @@ spConfiguration:
     title: 'Configuration'
     description: 'In the configuration module, basic StreamPipes settings and services can be configured.'
     icon: 'settings'
-    homeImage: '/assets/img/home/configuration.png'
     pageNames: 'PageName.SETTINGS'
     admin: True
+    showStatusBox: false
 spAppOverview:
     componentImport: False
     ng5_moduleName: 'AppOverviewModule'
@@ -94,9 +96,9 @@ spAppOverview:
     title: 'Apps'
     description: 'The app overview lets you access additional plugins.'
     icon: 'apps'
-    homeImage: '/assets/img/home/configuration.png'
     admin: false
     pageNames: 'PageName.APPS'
+    showStatusBox: false
 spDashboard:
     componentImport: False
     ng5_moduleName: 'DashboardModule'
@@ -108,9 +110,17 @@ spDashboard:
     title: 'Dashboard'
     description: 'The live dashboard visualizes data in real-time.'
     icon: 'insert_chart'
-    homeImage: '/assets/img/home/configuration.png'
     admin: false
     pageNames: 'PageName.DASHBOARD'
+    showStatusBox: true
+    statusBox:
+        link: "['dashboard']"
+        createLinks: "['dashboard']"
+        title: 'Dashboards'
+        createTitle: 'New dashboard'
+        dataFns: '[this.dashboardService.getDashboards()]'
+        viewRoles: '[UserRole.ROLE_DASHBOARD_ADMIN, UserRole.ROLE_DASHBOARD_USER]'
+        createRoles: '[UserRole.ROLE_DASHBOARD_ADMIN]'
 spDataExplorer:
     componentImport: False
     ng5_moduleName: 'DataExplorerModule'
@@ -122,9 +132,17 @@ spDataExplorer:
     title: 'Data Explorer'
     description: 'The data explorer lets you visually inspect historical data from your connected sources.'
     icon: 'search'
-    homeImage: '/assets/img/home/configuration.png'
     admin: false
     pageNames: 'PageName.DATA_EXPLORER'
+    showStatusBox: true
+    statusBox:
+        link: "['dataexplorer']"
+        createLinks: "['dataexplorer']"
+        title: 'Data Views'
+        createTitle: 'New data view'
+        dataFns: '[this.dataViewService.getDataViews()]'
+        viewRoles: '[UserRole.ROLE_DATA_EXPLORER_ADMIN, UserRole.ROLE_DATA_EXPLORER_USER]'
+        createRoles: '[UserRole.ROLE_DATA_EXPLORER_ADMIN]'
 spFiles:
     componentImport: False
     ng5_moduleName: 'FilesModule'
@@ -136,6 +154,6 @@ spFiles:
     title: 'File Management'
     description: 'The file management module lets you upload and manage files that are used by adapters or pipeline elements.'
     icon: 'folder'
-    homeImage: '/assets/img/home/configuration.png'
     admin: true
     pageNames: 'PageName.FILE_UPLOAD'
+    showStatusBox: false
diff --git a/ui/deployment/prebuild.js b/ui/deployment/prebuild.js
index 460ecd1624..d4628ea5ba 100644
--- a/ui/deployment/prebuild.js
+++ b/ui/deployment/prebuild.js
@@ -79,7 +79,8 @@ for (let module of config.modules) {
         icon: modules[module]['icon'],
         admin: modules[module]['admin'],
         description: modules[module]['description'],
-        homeImage: modules[module]['homeImage'],
+        showStatusBox: modules[module]['showStatusBox'],
+        statusBox: modules[module]['statusBox'],
     });
     console.log('Active Angular Module: ' + module);
 }
diff --git a/ui/src/app/connect/dialog/adapter-started/adapter-started-success/adapter-started-success.component.html b/ui/src/app/connect/dialog/adapter-started/adapter-started-success/adapter-started-success.component.html
index 85346b2101..b600a282ae 100644
--- a/ui/src/app/connect/dialog/adapter-started/adapter-started-success/adapter-started-success.component.html
+++ b/ui/src/app/connect/dialog/adapter-started/adapter-started-success/adapter-started-success.component.html
@@ -67,6 +67,7 @@
     </div>
     <div *ngIf="!adapterStatus.success">
         <div
+            data-cy="sp-connect-adapter-error-message"
             class="info-message warning-message"
             fxFlex="100"
             fxLayoutAlign="center center"
diff --git a/ui/src/app/connect/dialog/edit-event-property/components/edit-value-transformation/edit-timestamp-property/edit-timestamp-property.component.html b/ui/src/app/connect/dialog/edit-event-property/components/edit-value-transformation/edit-timestamp-property/edit-timestamp-property.component.html
index 72ebad85f1..da7236b67b 100644
--- a/ui/src/app/connect/dialog/edit-event-property/components/edit-value-transformation/edit-timestamp-property/edit-timestamp-property.component.html
+++ b/ui/src/app/connect/dialog/edit-event-property/components/edit-value-transformation/edit-timestamp-property/edit-timestamp-property.component.html
@@ -55,6 +55,7 @@
             <mat-select
                 placeholder="Current time unit"
                 [(value)]="selectedTimeMultiplier"
+                data-cy="connect-timestamp-number-dropdown"
             >
                 <mat-option
                     value="milliseconds"
diff --git a/ui/src/app/home/components/status.component.html b/ui/src/app/home/components/status.component.html
index 2ccda56988..6a7f46bd1f 100644
--- a/ui/src/app/home/components/status.component.html
+++ b/ui/src/app/home/components/status.component.html
@@ -16,53 +16,44 @@
   ~
   -->
 
-<div fxFlex="100" fxLayout="row">
-    <div
-        fxFlex="25"
-        class="status-container pipelines mr-20"
-        (click)="navigate('pipelines')"
-    >
-        <div fxFill="100" fxLayout="column" fxLayoutAlign="center center">
-            <div class="status-container-number" fxLayoutAlign="start center">
-                {{ availablePipelines }}
+<div
+    fxFlex="100"
+    class="status-container p-10"
+    (click)="navigate(statusBox.link)"
+>
+    <div fxLayout="row" fxFlex="100">
+        <div fxFlex fxLayout="column" fxLayoutAlign="start start">
+            <div fxFlex fxLayout="column">
+                <div class="status-container-number">
+                    <span>{{ resourceCount }}</span>
+                </div>
+                <div
+                    class="status-container-text"
+                    fxFlex
+                    fxLayoutAlign="end end"
+                >
+                    <span>{{ statusBox.title }}</span>
+                </div>
             </div>
-            <div class="status-container-text">pipelines created</div>
-        </div>
-    </div>
-    <div
-        fxFlex="25"
-        class="status-container pipelines-running mr-20"
-        (click)="navigate('pipelines')"
-    >
-        <div fxFill="100" fxLayout="column" fxLayoutAlign="center center">
-            <div class="status-container-number" fxLayoutAlign="start center">
-                {{ runningPipelines }}
+            <div fxFlex="100" fxLayoutAlign="end end" class="w-100">
+                <div
+                    fxFlex="100"
+                    fxLayoutAlign="start center"
+                    *ngIf="showCreateLink"
+                >
+                    <a
+                        (click)="navigate(statusBox.createLink)"
+                        fxLayoutAlign="start center"
+                        class="status-container-create-link"
+                    >
+                        <i class="material-icons">add_circle</i>
+                        <span>&nbsp;{{ statusBox.createTitle }}</span>
+                    </a>
+                </div>
             </div>
-            <div class="status-container-text">pipelines running</div>
         </div>
     </div>
-    <div
-        fxFlex="25"
-        class="status-container notifications mr-20"
-        (click)="navigate('notifications')"
-    >
-        <div fxFill="100" fxLayout="column" fxLayoutAlign="center center">
-            <div class="status-container-number" fxLayoutAlign="start center">
-                {{ unreadNotificationCount }}
-            </div>
-            <div class="status-container-text">unread notifications</div>
-        </div>
-    </div>
-    <div
-        fxFlex="25"
-        class="status-container pipeline-elements-installed"
-        (click)="navigate('add')"
-    >
-        <div fxFill="100" fxLayout="column" fxLayoutAlign="center center">
-            <div class="status-container-number" fxLayoutAlign="start center">
-                {{ availablePipelineElements }}
-            </div>
-            <div class="status-container-text">installed pipeline elements</div>
-        </div>
+    <div fxLayoutAlign="start start">
+        <i class="material-icons status-container-icon">{{ statusBox.icon }}</i>
     </div>
 </div>
diff --git a/ui/src/app/home/components/status.component.scss b/ui/src/app/home/components/status.component.scss
index 500aeec2d0..d37adca327 100644
--- a/ui/src/app/home/components/status.component.scss
+++ b/ui/src/app/home/components/status.component.scss
@@ -18,30 +18,18 @@
 
 .status-container {
     height: 150px;
-    width: 80%;
-    color: white;
+    width: 100%;
+    color: var(--color-primary);
     border-radius: 5px;
     margin-bottom: 20px;
-}
-
-.status-container.pipelines {
-    background: var(--color-accent);
-}
-
-.status-container.pipelines-running {
-    background: rgb(59, 92, 149);
-}
-
-.status-container.notifications {
-    background: rgb(88, 114, 146);
-}
-
-.status-container.pipeline-elements-installed {
-    background: rgb(156, 156, 156);
+    border: 2px solid var(--color-bg-2);
+    background: var(--color-bg-2);
+    border-bottom: 5px solid var(--color-accent);
 }
 
 .status-container-number {
-    font-size: 46pt;
+    font-size: 36pt;
+    font-weight: bold;
 }
 
 .status-container:hover {
@@ -50,9 +38,19 @@
 }
 
 .status-container-text {
-    font-size: 16pt;
+    font-size: 15pt;
+}
+
+.status-container-create-link {
+    font-size: 12pt;
+    color: var(--color-accent);
+}
+
+.status-container-create-link:hover {
+    font-weight: bold;
 }
 
-.mr-20 {
-    margin-right: 20px;
+.status-container-icon {
+    font-size: 50pt;
+    color: var(--color-bg-3);
 }
diff --git a/ui/src/app/home/components/status.component.ts b/ui/src/app/home/components/status.component.ts
index 6a22d8c2a4..08ba999a59 100644
--- a/ui/src/app/home/components/status.component.ts
+++ b/ui/src/app/home/components/status.component.ts
@@ -18,11 +18,10 @@
 
 import { Component, Input, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
-import { NotificationCountService } from '../../services/notification-count-service';
-import {
-    PipelineElementService,
-    PipelineService,
-} from '@streampipes/platform-services';
+import { UserInfo } from '@streampipes/platform-services';
+import { StatusBox } from '../models/home.model';
+import { UserRole } from '../../_enums/user-role.enum';
+import { zip } from 'rxjs';
 
 @Component({
     selector: 'sp-status',
@@ -30,31 +29,49 @@ import {
     styleUrls: ['./status.component.scss'],
 })
 export class StatusComponent implements OnInit {
-    unreadNotificationCount = 0;
-
     @Input()
-    availablePipelines = 0;
+    statusBox: StatusBox;
 
     @Input()
-    availablePipelineElements = 0;
+    resourceCount: number = 0;
 
     @Input()
-    runningPipelines = 0;
+    currentUser: UserInfo;
+
+    showCreateLink = true;
 
-    constructor(
-        private pipelineElementService: PipelineElementService,
-        private router: Router,
-        public notificationCountService: NotificationCountService,
-        private pipelineService: PipelineService,
-    ) {}
+    constructor(private router: Router) {}
 
     ngOnInit() {
-        this.notificationCountService.unreadNotificationCount$.subscribe(
-            count => (this.unreadNotificationCount = count),
-        );
+        zip(this.statusBox.dataFns).subscribe(res => {
+            let totalLength = 0;
+            res.forEach(response => {
+                totalLength += response.length;
+            });
+
+            this.resourceCount = totalLength;
+        });
+        this.showCreateLink = this.shouldShowCreateLink();
+    }
+
+    shouldShowCreateLink(): boolean {
+        if (this.statusBox.createRoles.length === 0) {
+            return false;
+        } else if (this.hasRole(UserRole.ROLE_ADMIN)) {
+            return true;
+        } else {
+            const userRoles = this.currentUser.roles;
+            return userRoles.some(role =>
+                this.statusBox.createRoles.includes(role as UserRole),
+            );
+        }
+    }
+
+    hasRole(role: UserRole): boolean {
+        return this.currentUser.roles.indexOf(role) > -1;
     }
 
-    navigate(url: string) {
-        this.router.navigate([url]);
+    navigate(command: string[]) {
+        this.router.navigate(command);
     }
 }
diff --git a/ui/src/app/home/home.component.html b/ui/src/app/home/home.component.html
index 7bb10fca25..9912bca530 100644
--- a/ui/src/app/home/home.component.html
+++ b/ui/src/app/home/home.component.html
@@ -24,25 +24,25 @@
                     <div class="welcome-text">Welcome!</div>
                 </div>
                 <div fxLayout="column" fxFlex="100" class="home-margin">
-                    <div fxFlex="100" fxLayoutAlign="start center">
+                    <div
+                        fxFlex="100"
+                        fxLayout="row"
+                        fxLayoutAlign="start center"
+                        fxLayoutGap="10px"
+                        *ngIf="showStatus"
+                    >
                         <sp-status
-                            fxFlex="100"
-                            [availablePipelineElements]="
-                                availablePipelineElements.length
-                            "
-                            [availablePipelines]="availablePipelines.length"
-                            [runningPipelines]="runningPipelines.length"
-                            *ngIf="showStatus"
+                            [fxFlex]="100 / statusBoxes.length"
+                            [statusBox]="box"
+                            [currentUser]="currentUser"
+                            *ngFor="let box of statusBoxes"
                         ></sp-status>
                     </div>
                     <div fxFlex="100">
-                        <div
-                            class="assemblyOptions sp-blue-bg mt-0 round-border"
-                            style="padding: 5px"
+                        <sp-basic-inner-panel
+                            innerPadding="0"
+                            panelTitle="{{ appConstants.APP_NAME }} Modules"
                         >
-                            <h4>{{ appConstants.APP_NAME }} Modules</h4>
-                        </div>
-                        <div class="sp-blue-border pt-0" fxLayout="column">
                             <div fxFlex="100" fxLayout="column">
                                 <mat-list class="modules-list">
                                     <mat-list-item
@@ -75,7 +75,7 @@
                                     </mat-list-item>
                                 </mat-list>
                             </div>
-                        </div>
+                        </sp-basic-inner-panel>
                     </div>
                 </div>
             </div>
diff --git a/ui/src/app/home/home.component.ts b/ui/src/app/home/home.component.ts
index 0cb6585c43..e5e4e47c7a 100644
--- a/ui/src/app/home/home.component.ts
+++ b/ui/src/app/home/home.component.ts
@@ -40,6 +40,7 @@ import {
     UserInfo,
 } from '@streampipes/platform-services';
 import { zip } from 'rxjs';
+import { StatusBox } from './models/home.model';
 
 @Component({
     templateUrl: './home.component.html',
@@ -54,6 +55,8 @@ export class HomeComponent implements OnInit {
     availablePipelines: Pipeline[] = [];
     runningPipelines: Pipeline[] = [];
 
+    statusBoxes: StatusBox[] = [];
+
     requiredAdapterForTutorialAppId: any =
         'org.apache.streampipes.connect.iiot.adapters.simulator.machine';
     requiredProcessorForTutorialAppId: any =
@@ -78,12 +81,16 @@ export class HomeComponent implements OnInit {
         private adapterService: AdapterService,
     ) {
         this.serviceLinks = this.homeService.getFilteredServiceLinks();
+        this.statusBoxes = this.homeService
+            .getFilteredServiceLinks()
+            .filter(s => s.showStatusBox)
+            .map(s => s.statusBox);
     }
 
     ngOnInit() {
         this.currentUser = this.currentUserService.getCurrentUser();
         const isAdmin = this.hasRole(UserRole.ROLE_ADMIN);
-        this.showStatus = isAdmin || this.hasRole(UserRole.ROLE_PIPELINE_ADMIN);
+        this.showStatus = true;
         if (isAdmin) {
             this.loadResources();
         }
@@ -214,7 +221,6 @@ export class HomeComponent implements OnInit {
                 .concat(...res[2])
                 .concat(...res[3])
                 .concat(...res[4]);
-
             this.checkForTutorial();
         });
     }
diff --git a/ui/src/app/home/home.module.ts b/ui/src/app/home/home.module.ts
index 469762ddc3..7693633a09 100644
--- a/ui/src/app/home/home.module.ts
+++ b/ui/src/app/home/home.module.ts
@@ -29,6 +29,7 @@ import { MatButtonModule } from '@angular/material/button';
 import { MatListModule } from '@angular/material/list';
 import { PlatformServicesModule } from '@streampipes/platform-services';
 import { WelcomeTourComponent } from './dialog/welcome-tour/welcome-tour.component';
+import { SharedUiModule } from '@streampipes/shared-ui';
 
 @NgModule({
     imports: [
@@ -40,6 +41,7 @@ import { WelcomeTourComponent } from './dialog/welcome-tour/welcome-tour.compone
         MatDividerModule,
         MatListModule,
         PlatformServicesModule,
+        SharedUiModule,
     ],
     declarations: [HomeComponent, StatusComponent, WelcomeTourComponent],
     providers: [HomeService],
diff --git a/ui/src/app/home/models/home.model.ts b/ui/src/app/home/models/home.model.ts
new file mode 100644
index 0000000000..d27ae16a27
--- /dev/null
+++ b/ui/src/app/home/models/home.model.ts
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Observable } from 'rxjs';
+import { PageName } from '../../_enums/page-name.enum';
+import { UserRole } from '../../_enums/user-role.enum';
+
+export interface StatusBox {
+    link: string[];
+    createLink: string[];
+    title: string;
+    createTitle: string;
+    dataFns: Observable<any>[];
+    viewRoles: UserRole[];
+    createRoles: UserRole[];
+    icon: string;
+}
+
+export interface Link {
+    newWindow: boolean;
+    value: string;
+}
+
+export interface ServiceLink {
+    name: string;
+    description: string;
+    icon: string;
+    pageNames: PageName[];
+    link: Link;
+    showStatusBox: boolean;
+    statusBox?: StatusBox;
+    disableCreateLink?: boolean;
+}


Test patch:
diff --git a/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/SchemaEventTransformerTest.java b/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/SchemaEventTransformerTest.java
index ec87937de1..56400a8066 100644
--- a/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/SchemaEventTransformerTest.java
+++ b/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/schema/SchemaEventTransformerTest.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.connect.shared.preprocessing.transform.schema;
 
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
diff --git a/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/ValueEventTransformerTest.java b/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/ValueEventTransformerTest.java
index 89cd25be8a..286be99a00 100644
--- a/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/ValueEventTransformerTest.java
+++ b/streampipes-connect-shared/src/test/java/org/apache/streampipes/connect/shared/preprocessing/transform/value/ValueEventTransformerTest.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.connect.shared.preprocessing.transform.value;
 
-import org.apache.streampipes.connect.shared.preprocessing.transform.TransformationRule;
+import org.apache.streampipes.extensions.api.connect.TransformationRule;
 import org.apache.streampipes.model.schema.EventProperty;
 import org.apache.streampipes.model.schema.EventPropertyPrimitive;
 import org.apache.streampipes.model.schema.EventSchema;
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/protocol/stream/FileReplayAdapterTest.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/protocol/stream/FileReplayAdapterTest.java
new file mode 100644
index 0000000000..8a6c3d91a2
--- /dev/null
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/test/java/org/apache/streampipes/connect/iiot/protocol/stream/FileReplayAdapterTest.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.connect.iiot.protocol.stream;
+
+import org.apache.streampipes.commons.exceptions.connect.AdapterException;
+import org.apache.streampipes.connect.shared.preprocessing.transform.value.TimestampTranformationRuleMode;
+import org.apache.streampipes.extensions.api.connect.IEventCollector;
+import org.apache.streampipes.extensions.api.extractor.IAdapterParameterExtractor;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription;
+import org.apache.streampipes.model.connect.rules.value.TimestampTranfsformationRuleDescription;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+class FileReplayAdapterTest {
+
+  private FileReplayAdapter fileReplayAdapter;
+  private IEventCollector collector;
+  private static final String TIMESTAMP = "timestamp";
+  private static final long TIMESTAMP_VALUE = 1622544682000L;
+  private Map<String, Object> event;
+  private ArgumentCaptor<Map<String, Object>> resultEventCapture;
+  private IAdapterParameterExtractor extractor;
+  private AdapterDescription adapterDescription;
+
+  @BeforeEach
+  void setUp() {
+    collector = mock(IEventCollector.class);
+    extractor = mock(IAdapterParameterExtractor.class);
+    adapterDescription = mock(AdapterDescription.class);
+    when(extractor.getAdapterDescription()).thenReturn(adapterDescription);
+    fileReplayAdapter = new FileReplayAdapter();
+    fileReplayAdapter.setTimestampSourceFieldName(TIMESTAMP);
+    event = new HashMap<>();
+    resultEventCapture = ArgumentCaptor.forClass(Map.class);
+  }
+
+
+  @Test
+  public void testThrowExceptionWhenAddTimestampRuleIsSelected_withAddTimestampRule() {
+    when(adapterDescription.getRules()).thenReturn(List.of(new AddTimestampRuleDescription()));
+
+    assertThrows(
+        AdapterException.class,
+        () -> FileReplayAdapter.throwExceptionWhenAddTimestampRuleIsSelected(extractor)
+    );
+  }
+
+  @Test
+  public void testThrowExceptionWhenAddTimestampRuleIsSelected_withoutAddTimestampRule() {
+    when(adapterDescription.getRules()).thenReturn(Collections.emptyList());
+  }
+
+  @Test
+  void processEvent_shouldCollectEventWhenTimestampIsLong() throws AdapterException {
+    event.put(TIMESTAMP, TIMESTAMP_VALUE);
+
+    fileReplayAdapter.processEvent(collector, event);
+
+    verify(collector, times(1)).collect(event);
+  }
+
+  @Test
+  void processEvent_shouldCollectEventWhenTimestampIsInteger() throws AdapterException {
+    event.put(TIMESTAMP, 1622544682);
+
+    fileReplayAdapter.processEvent(collector, event);
+
+    verify(collector, times(1)).collect(event);
+  }
+
+  @Test
+  void processEvent_shouldThrowAdapterExceptionWhenTimestampIsNotUnixTimestampInMs() {
+    event.put(TIMESTAMP, "not a timestamp");
+
+    assertThrows(AdapterException.class, () -> fileReplayAdapter.processEvent(collector, event));
+  }
+
+  @Test
+  void processEvent_shouldNotCollectEventWhenTimestampCouldNotBeProcessed()  {
+    event.put(TIMESTAMP, -1);
+
+    assertThrows(AdapterException.class, () -> fileReplayAdapter.processEvent(collector, event));
+  }
+
+  @Test
+  void processEvent_shouldCollectEventWhenTimestampIsReplaced() throws AdapterException {
+    event.put(TIMESTAMP, TIMESTAMP_VALUE);
+
+    fileReplayAdapter.setReplaceTimestamp(true);
+    fileReplayAdapter.processEvent(collector, event);
+
+    verify(collector, times(1)).collect(resultEventCapture.capture());
+
+    var restultEvent = resultEventCapture.getValue();
+    assertEquals(restultEvent.size(), 1);
+    assertNotEquals(restultEvent.get(TIMESTAMP), TIMESTAMP_VALUE);
+  }
+
+
+  @Test
+  void getTimestampFromEvent_returnsLongTimestamp() throws AdapterException {
+    event.put(TIMESTAMP, TIMESTAMP_VALUE);
+
+    long actualEventTimestamp = fileReplayAdapter.getTimestampFromEvent(event);
+
+    assertEquals(TIMESTAMP_VALUE, actualEventTimestamp);
+  }
+
+  @Test
+  void getTimestampFromEvent_withLongInSeconds() throws AdapterException {
+    setupNumberTransformationRule(1622544682L);
+    assertEventTimestamp(TIMESTAMP_VALUE);
+  }
+
+  @Test
+  void getTimestampFromEvent_withIntegerInSeconds() throws AdapterException {
+    setupNumberTransformationRule(1622544682);
+    assertEventTimestamp(TIMESTAMP_VALUE);
+  }
+
+  @Test
+  void getTimestampFromEvent_withStringFormatInUtc() throws AdapterException {
+    setupStringTransformationRule(
+        "2024-07-01T12:00:00.000Z",
+        "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
+    );
+    assertEventTimestamp(1719835200000L);
+  }
+
+  @Test
+  void getTimestampFromEvent_withStringFormatAndPositiveOffset() throws AdapterException {
+    setupStringTransformationRule(
+        "2024-07-01T12:00:00.000+03:00",
+        "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"
+    );
+    assertEventTimestamp(1719824400000L);
+  }
+
+  @Test
+  void getTimestampFromEvent_withStringFormatAndNegativeOffset() throws AdapterException {
+    setupStringTransformationRule(
+        "2024-07-01T05:00:00.000-10:00",
+        "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"
+    );
+    assertEventTimestamp(1719846000000L);
+  }
+
+  @Test
+  void getTimestampFromEvent_withStringFormatAndZeroOffset() throws AdapterException {
+    setupStringTransformationRule(
+        "2024-07-01T12:00:00.000+00:00",
+        "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"
+    );
+    assertEventTimestamp(1719835200000L);
+  }
+
+  @Test
+  void getTimestampFromEvent_withStringFormatWithoutTimeZone() throws AdapterException {
+    setupStringTransformationRule(
+        "01.07.2024 12:00:00",
+        "dd.MM.yyyy HH:mm:ss"
+    );
+    assertEventTimestamp(1719835200000L);
+  }
+
+
+  /**
+   * Sets up a string transformation rule for timestamp processing.
+   * This method configures a transformation rule that interprets and converts
+   * a timestamp string according to the specified format string.
+   */
+  private void setupStringTransformationRule(
+      String timestampValue,
+      String formatString
+  ) {
+    event.put(TIMESTAMP, timestampValue);
+
+    var rule = new TimestampTranfsformationRuleDescription();
+    rule.setRuntimeKey(TIMESTAMP);
+    rule.setMode(TimestampTranformationRuleMode.FORMAT_STRING.internalName());
+    rule.setFormatString(formatString);
+
+    fileReplayAdapter.setTimestampTranfsformationRuleDescription(rule);
+  }
+
+  /**
+   * Sets up a number transformation rule for timestamp processing.
+   * This method configures a transformation rule that multiplies a given timestamp value
+   * by a specified multiplier.
+   */
+  private void setupNumberTransformationRule(
+      long timestampValue
+  ) {
+    event.put(TIMESTAMP, timestampValue);
+
+    var rule = new TimestampTranfsformationRuleDescription();
+    rule.setRuntimeKey(TIMESTAMP);
+    rule.setMode(TimestampTranformationRuleMode.TIME_UNIT.internalName());
+    rule.setMultiplier(1000L);
+
+    fileReplayAdapter.setTimestampTranfsformationRuleDescription(rule);
+  }
+
+  private void assertEventTimestamp(long expected) throws AdapterException {
+    assertEquals(expected, fileReplayAdapter.getTimestampFromEvent(event));
+  }
+
+}
\ No newline at end of file
diff --git a/ui/cypress/tests/adapter/fileStream.smoke.spec.ts b/ui/cypress/tests/adapter/fileStream.smoke.spec.ts
index 262228877b..7024335b05 100644
--- a/ui/cypress/tests/adapter/fileStream.smoke.spec.ts
+++ b/ui/cypress/tests/adapter/fileStream.smoke.spec.ts
@@ -22,7 +22,7 @@ import { AdapterBuilder } from '../../support/builder/AdapterBuilder';
 import { ConnectBtns } from '../../support/utils/connect/ConnectBtns';
 
 describe(
-    'Test File Stream Adapter',
+    'Test File Replay Adapter',
     {
         retries: {
             runMode: 4,
@@ -32,10 +32,10 @@ describe(
     () => {
         beforeEach('Setup Test', () => {
             cy.initStreamPipesTest();
-            FileManagementUtils.addFile('fileTest/random.csv');
         });
 
-        it('Perform Test', () => {
+        it('Test successful adapter generation for file stream adapter', () => {
+            FileManagementUtils.addFile('fileTest/random.csv');
             const adapterInput = AdapterBuilder.create('File_Stream')
                 .setName('File Stream Adapter Test')
                 .setTimestampProperty('timestamp')
@@ -48,5 +48,58 @@ describe(
             ConnectUtils.testAdapter(adapterInput);
             ConnectUtils.deleteAdapter();
         });
+
+        it('File stream adapter should not allow add timestamp option in schema editor', () => {
+            FileManagementUtils.addFile('connect/fileReplay/noTimestamp.csv');
+            const adapterInput = AdapterBuilder.create('File_Stream')
+                .setName('File Stream Adapter Test')
+                .setAutoAddTimestampPropery()
+                .setFormat('csv')
+                .addFormatInput('input', ConnectBtns.csvDelimiter(), ';')
+                .addFormatInput('checkbox', ConnectBtns.csvHeader(), 'check')
+                .build();
+
+            ConnectUtils.testAdapter(adapterInput, true);
+        });
+
+        // it('File Stream adapter with unix timestamp in seconds', () => {
+        //     FileManagementUtils.addFile('connect/fileReplay/timestampInSeconds/input.csv');
+        //     const adapterConfiguration =
+        //         ConnectUtils.setUpPreprocessingRuleTest(false);
+        //
+        //     // Edit timestamp property
+        //     ConnectEventSchemaUtils.editTimestampPropertyWithNumber(
+        //         'timestamp',
+        //         'Seconds',
+        //     );
+        //
+        //     ConnectEventSchemaUtils.finishEventSchemaConfiguration();
+        //     ConnectUtils.tearDownPreprocessingRuleTest(
+        //         adapterConfiguration,
+        //         'cypress/fixtures/connect/fileReplay/timestampInSeconds/expected.csv',
+        //         false,
+        //         2000,
+        //     );
+        // });
+
+        // it('File Stream adapter with unix timestamp in milliseconds', () => {
+        //     FileManagementUtils.addFile('connect/fileReplay/timestampInMilliseconds/input.csv');
+        //     const adapterConfiguration =
+        //         ConnectUtils.setUpPreprocessingRuleTest(false);
+        //
+        //     // Edit timestamp property
+        //     ConnectEventSchemaUtils.editTimestampPropertyWithNumber(
+        //         'timestamp',
+        //         'Milliseconds',
+        //     );
+        //
+        //     ConnectEventSchemaUtils.finishEventSchemaConfiguration();
+        //     ConnectUtils.tearDownPreprocessingRuleTest(
+        //         adapterConfiguration,
+        //         'cypress/fixtures/connect/fileReplay/timestampInMilliseconds/expected.csv',
+        //         false,
+        //         2000,
+        //     );
+        // });
     },
 );
diff --git a/ui/cypress/tests/adapter/rules/schemaRules.smoke.spec.ts b/ui/cypress/tests/adapter/rules/schemaRules.smoke.spec.ts
index 1aa4d14ce6..673cf31ebf 100644
--- a/ui/cypress/tests/adapter/rules/schemaRules.smoke.spec.ts
+++ b/ui/cypress/tests/adapter/rules/schemaRules.smoke.spec.ts
@@ -43,7 +43,7 @@ describe('Connect schema rule transformations', () => {
         );
 
         // Add a timestamp property
-        ConnectEventSchemaUtils.addTimestampProperty();
+        ConnectEventSchemaUtils.markPropertyAsTimestamp('timestamp');
 
         ConnectEventSchemaUtils.finishEventSchemaConfiguration();
 
diff --git a/ui/cypress/tests/adapter/rules/valueRules.ts b/ui/cypress/tests/adapter/rules/valueRules.spec.ts
similarity index 97%
rename from ui/cypress/tests/adapter/rules/valueRules.ts
rename to ui/cypress/tests/adapter/rules/valueRules.spec.ts
index 2163f454d2..b4b698e2a9 100644
--- a/ui/cypress/tests/adapter/rules/valueRules.ts
+++ b/ui/cypress/tests/adapter/rules/valueRules.spec.ts
@@ -31,7 +31,7 @@ describe('Connect value rule transformations', () => {
             ConnectUtils.setUpPreprocessingRuleTest(false);
 
         // Edit timestamp property
-        ConnectEventSchemaUtils.editTimestampProperty(
+        ConnectEventSchemaUtils.editTimestampPropertyWithRegex(
             'timestamp',
             "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
         );