diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml new file mode 100644 index 000000000..2c77176e0 --- /dev/null +++ b/.github/workflows/test-build.yml @@ -0,0 +1,63 @@ +name: mfr_ci_testing + +on: [push, pull_request, workflow_dispatch] + +jobs: + + build: + runs-on: ubuntu-20.04 + env: + GHA_DISTRO: ubuntu-20.04 + if: "!contains(github.event.head_commit.message, 'skip ci')" + strategy: + matrix: + python-version: [3.6] + steps: + - name: Git checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Cache Build Requirements + id: pip-cache-step + uses: actions/cache@v2 + with: + path: ${{ env.pythonLocation }} + key: ${{ env.GHA_DISTRO }}-${{ env.pythonLocation }}-${{ hashFiles('requirements.txt', 'dev-requirements.txt') }} + - name: install dependencies + if: steps.pip-cache-step.outputs.cache-hit != 'true' + run: | + python -m pip install --upgrade pip + pip install -r dev-requirements.txt + + runtests: + name: Run unit tests + needs: build + runs-on: ubuntu-20.04 + env: + GHA_DISTRO: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.6 + uses: actions/setup-python@v2 + with: + python-version: 3.6 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ${{ env.pythonLocation }} + key: ${{ env.GHA_DISTRO }}-${{ env.pythonLocation }}-${{ hashFiles('requirements.txt', 'dev-requirements.txt') }} + - name: run syntax checks + run: | + flake8 . + - name: build plugins + run: | + python setup.py develop + - name: run unit tests + run: | + py.test --cov-report term-missing --cov mfr tests + - name: Upload coverage data to coveralls.io + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0ead89595..000000000 --- a/.travis.yml +++ /dev/null @@ -1,41 +0,0 @@ -language: python - -python: - - "3.5" - - "3.6" - -sudo: false - -cache: - directories: - - $HOME/wheelhouse - -before_install: - - export WHEELHOUSE=$HOME/wheelhouse - -install: - - travis_retry pip install --upgrade pip - - travis_retry pip install setuptools==37.0.0 - - travis_retry pip install wheel==0.26.0 - - travis_retry pip install invoke==0.13.0 - - travis_retry invoke wheelhouse --develop - - travis_retry invoke install --develop - -env: - - MFR_CONFIG=./travis-config.json - -script: - - invoke test - -before_cache: - # force reinstall for reqs pulled from github repos - - rm -f $HOME/wheelhouse/xlrd-*.whl - - rm -f $HOME/wheelhouse/waterbutler-*.whl - - rm -f $HOME/wheelhouse/aiohttpretty-*.whl - - -notifications: - flowdock: 0221882cdda034c0e9ac2a0e766053dd - -after_success: - coveralls diff --git a/AUTHORS.rst b/AUTHORS.rst index a1565ccb9..d2f37d827 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -34,3 +34,4 @@ Contributors - Longze Chen `@cslzchen `_ - Jonathon Love `@jonathon-love `_ - Josh Bird `@birdbrained `_ +- Connor Bailey `@cbbcbail `_ diff --git a/README.md b/README.md index 14187c90e..cf2a5382c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # MFR (Modular File Renderer) -`master` Build Status: [![Build Status](https://travis-ci.org/CenterForOpenScience/modular-file-renderer.svg?branch=master)](https://travis-ci.org/CenterForOpenScience/modular-file-renderer) +`master` Build Status: ![Build Status](https://github.com/CenterForOpenScience/modular-file-renderer/actions/workflows/test-build.yml/badge.svg?branch=master)[![Coverage Status](https://coveralls.io/repos/github/CenterForOpenScience/modular-file-renderer/badge.svg?branch=master)](https://coveralls.io/github/CenterForOpenScience/modular-file-renderer?branch=master) -`develop` Build Status: [![Build Status](https://travis-ci.org/CenterForOpenScience/modular-file-renderer.svg?branch=develop)](https://travis-ci.org/CenterForOpenScience/modular-file-renderer) - -[![Coverage Status](https://coveralls.io/repos/github/CenterForOpenScience/modular-file-renderer/badge.svg)](https://coveralls.io/github/CenterForOpenScience/modular-file-renderer) +`develop` Build Status: ![Build Status](https://github.com/CenterForOpenScience/modular-file-renderer/actions/workflows/test-build.yml/badge.svg?branch=develop)[![Coverage Status](https://coveralls.io/repos/github/CenterForOpenScience/modular-file-renderer/badge.svg?branch=develop)](https://coveralls.io/github/CenterForOpenScience/modular-file-renderer?branch=develop) A Python package for rendering files to HTML via an embeddable iframe. diff --git a/configuringDevEnv.md b/configuringDevEnv.md new file mode 100644 index 000000000..fed57a399 --- /dev/null +++ b/configuringDevEnv.md @@ -0,0 +1,94 @@ +# Configuring the Development Environment for OSF: + +Instructions for MacOS Big Sur compiled from [**README-docker-compose.md**](https://github.com/CenterForOpenScience/osf.io/blob/develop/README-docker-compose.md), correspondence with Fitz Elliot, and the [documentation](https://cosdev.readthedocs.io/en/latest/osf/setup.html): modified to fix errors. + +- Clone the repository: + - ```$ git clone [osf.io]``` + - ```$ cd osf.io``` + +- Install the Docker Client + +- Grant Docker minimum resources of 1 CPU, 8GB memory, 2GB swap, and 32GB disk image size + +- Alias the loopback interface: + - ```$ export libdir='/Library/LaunchDaemons'``` + - ```$ export file='com.runlevel1.lo0.192.168.168.167.plist'``` + - ```$ sudo cp $file $libdir``` + - ```$ sudo chmod 0644 $libdir/$file``` + - ```$ sudo chown root:wheel $libdir/$file``` + - ```$ sudo launchctl load $libdir/$file``` + +- Configure application: + - ```$ cp ./website/settings/local-dist.py ./website/settings/local.py``` + - ```$ cp ./api/base/settings/local-dist.py ./api/base/settings/local.py``` + - ```$ cp ./docker-compose-dist.override.yml ./docker-compose.override.yml``` + - ```$ cp ./tasks/local-dist.py ./tasks/local.py``` + +- Start Application: + - ```$ docker compose up requirements mfr_requirements wb_requirements``` + - ```$ docker compose up -d elasticsearch postgres mongo rabbitmq``` + - ```$ rm -Rf ./node_modules``` + - ```$ docker compose up -d assets``` + - ```$ docker compose up -d admin_assets``` + - ```$ docker-compose up -d mfr wb fakecas sharejs``` + - ```$ docker-compose run --rm web python3 manage.py migrate``` + - ```$ docker-compose up -d worker web api admin preprints registries ember\_osf\_web``` + +- *Note: once it has been setup, you can [quickstart](https://github.com/CenterForOpenScience/osf.io/blob/develop/README-docker-compose.md\#quickstart-running-all-osf-services-in-the-background) to quickly launch in the future.* +- Access the OSF at **http://localhost:5000**. +- Click sign up to create an account. +- Access the 'web' containers logs to view the confirmation email for new accounts: +- ```$ docker compose logs -f --tail 1000 web``` +- Copy the confirmation link and paste into the browser to confirm the account then log in using the email address used to create the account. This allows test files, projects, and folders to be created and accessed through the normal methods. + + +## Instructions for building and testing the modified MFR container: + +- Go to the **modular-file-renderer** directory +- ```$ docker image build -t mfr:local .``` +- Go to the **osf.io** directory +- Edit the **docker-compose.yml** file. Under **mfr** and **mfr_requirements**, change the image to **image: mfr:local** +- ```$ docker compose up -d --force-recreate mfr_requirements``` +- ```$ docker compose up -d --force-recreate mfr``` +- Restart the Docker application + + +### In order to verify that the version of MFR you are running is the correct, most up to date version: + +- Modify the version number in **modular-file-renderer/mfr/version.py** to indicate that changes have been made. +- Check the version of the container at **http://localhost:7778/status** +- If the status version number matches the modified version number, you know that the modified version of the MFR container has been successfully built and deployed by Docker. + + +## Debugging Problems + +- Running out of memory in Docker can be solved by doing a system prune of the containers and reinstalling them. Allocating more memory in Docker is a good idea. +- ```$ docker system prune -a``` + + +# Configuring the development environment for MFR: +Instructions for MacOS Big Sur compiled from []**CONTRIBUTING.rst**](https://github.com/CenterForOpenScience/modular-file-renderer/blob/develop/CONTRIBUTING.rst), [**README.md**](https://github.com/CenterForOpenScience/modular-file-renderer/blob/develop/README.md), the [installation documentation](https://modular-file-renderer.readthedocs.io/en/latest/install.html\#install) and the [contribution documentation](https://modular-file-renderer.readthedocs.io/en/latest/contributing.html) and modified to fix errors. + +- Clone the repository: + - ```$ git clone [modular-file-renderer]``` + - ```$ cd modular-file-renderer``` +- Create the virtual environment: + - ```$ pip install virtualenv virtualenvwrapper``` + - ```$ brew update``` + - ```$ brew install python3 r pspp unoconv pyenv``` + +- Add the following initialization lines to either the **~/.bashrc** or **~/.zprofile** files for either bash or zsh shells respectively: +``` +eval "$(pyenv init -)" +eval "$(pyenv virtualenv-init -)" +``` + +- ```$ source ~/.[bashrc or zprofile]``` +- ```$ pyenv virtualenv 3.6.4 mfr``` +- ```$ pip install setuptools==37.0.0``` +- ```$ pip install invoke==0.13.0``` +- Modify **requirements.txt** to update the version number for **reportlab==3.4.0** to **reportlab==3.5.56** due to an incompatibility with the old version. +- ```$ invoke install -d``` + +- In order to run the development unit testing procedure: + - ```$ invoke test``` diff --git a/dev-requirements.txt b/dev-requirements.txt index 2ae08bb4a..37036c257 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -5,6 +5,7 @@ beautifulsoup4 colorlog==2.5.0 +coveralls flake8==3.0.4 ipdb mccabe @@ -12,5 +13,4 @@ pydevd==0.0.6 pyflakes pytest==2.8.2 pytest-cov==2.2.0 -python-coveralls==2.9.1 pyzmq==14.4.1 diff --git a/mfr/extensions/tdms/__init__.py b/mfr/extensions/tdms/__init__.py new file mode 100644 index 000000000..751fcda49 --- /dev/null +++ b/mfr/extensions/tdms/__init__.py @@ -0,0 +1 @@ +from .render import TdmsRenderer # noqa diff --git a/mfr/extensions/tdms/render.py b/mfr/extensions/tdms/render.py new file mode 100644 index 000000000..6330d804b --- /dev/null +++ b/mfr/extensions/tdms/render.py @@ -0,0 +1,134 @@ +import os +import base64 +from io import BytesIO + +import nptdms +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np +import datetime +from mako.lookup import TemplateLookup + +from mfr.core import extension +from mfr.core import utils + +class TdmsRenderer(extension.BaseRenderer): + + TEMPLATE = TemplateLookup( + directories=[ + os.path.join(os.path.dirname(__file__), 'templates') + ]).get_template('viewer.mako') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.metrics.add('nptdms_version', nptdms.version.__version__) + + def render(self): + """Render a tdms file to html.""" + maxTableLength = 100 # maximum rows to include in data table + minDigitLength = 4 # minumum digits before scientific notation is used + formatString = "{:." + str(minDigitLength) + "g}" + + fig, ax = plt.subplots() # create matplotlib figure object + ax.grid(True, alpha=0.1) # specify matplotlib grid properties + + # empty data structures to be filled with file data in loops + channelNames = [] + lineCollections = [] + properties = "" + data = pd.DataFrame() + + tdms_file = nptdms.TdmsFile.open(self.file_path, raw_timestamps=True) + fileMetadata = TdmsRenderer.formatMetadata(tdms_file.properties.items()) + + # Parse group data and metadata and generate html + for group in tdms_file.groups(): + groupClass = group.name.replace(" ", "") + buttonId = groupClass + "Button" + showHide = "showHide(\'" + groupClass + "\', \'" + buttonId + "\')" + rowTag = "" + buttonTag = "+" + properties += rowTag + "Group " + group.name + "" + properties += buttonTag + "" + + # Parse channel data and metadata and generate html + for channel in group.channels(): + channelClass = channel.name.replace(" ", "") + buttonId = channel.name.replace(" ", "") + "Button" + showHide = "showHide(\'" + channelClass + "\', \'" + buttonId + "\')" + rowTag = "" + buttonTag = "+" + properties += rowTag + "Channel " + channel.name + "" + buttonTag + "" + channelLength = len(channel) + + # Parse dictionary of properties: + for property, value in channel.properties.items(): + rowTag = "" + leftCol = rowTag + "" + property + "" + if utils.isfloat(value): # reformat float values + value = formatString.format(float(value)) + rightCol = "" + str(value) + "" + properties += leftCol + rightCol + + # Access numpy array of data for channel: + if (channelLength > 1): # Only access channels with datasets > 1 + # Plotting on a time axis + if (channel.properties['wf_start_time'] and channel.properties['wf_increment']): + start = channel.properties['wf_start_time'].as_datetime() + start = (start - datetime.datetime(1904, 1, 1)).total_seconds() + increment = channel.properties['wf_increment'] + stop = start + increment * channelLength + timeAxis = np.linspace(start, stop, channelLength) + line = ax.plot(timeAxis, channel, linewidth=2, label=channel.name) + plt.xticks(rotation=45) + plt.xlabel("Time (s)") + else: + line = ax.plot(channel, linewidth=2, label=channel.name) + + lineCollections.append(line) + channelNames.append(channel.name) + data[channel.name] = channel[:maxTableLength] + + ax.legend(channelNames, bbox_to_anchor=(1, 1)) + plotFile = BytesIO() # create byte sream object + fig.savefig(plotFile, format='png', bbox_inches='tight') # export plot to png in byte stream + encoded = base64.b64encode(plotFile.getvalue()).decode('utf-8') # encode base 64 byte stream data + plot = '\'data:image/png;base64,{}\''.format(encoded) # format encoded data as string + table = data.to_html() # export pandas DataFrame to HTML + + return self.TEMPLATE.render(base=self.assets_url, fileMetadata=fileMetadata, properties=properties, plot=plot, table=table) + + def formatMetadata(items): + # Parse property value pairs in file level metadata and generate html + + minDigitLength = 4 # minumum digits before scientific notation is used + formatString = "{:." + str(minDigitLength) + "G}" + fileMetadata = "" + + for property, value in items: + value = str(value) + if value.find("\n") > 1: + value = value.split("\n") + fileMetadata += "
  • " + value[0] + "
  • " + fileMetadata += "
      " + for v in value[1:]: + v = v.replace("\\n", " ").replace("\"", "").split("=") + v[0] = "".join(v[0].split(".")[1:]) + v[1] = v[1].strip() + if utils.isfloat(v[1]): # reformat float values + v[1] = formatString.format(float(v[1])) + fileMetadata += "
    • " + "= ".join(v) + "
    • " + fileMetadata += "
    " + else: + fileMetadata += "
  • " + str(property) + ": " + value + "
  • " + fileMetadata += "" + + return fileMetadata + + @property + def file_required(self): + return True + + @property + def cache_result(self): + return True diff --git a/mfr/extensions/tdms/static/css/tdms.css b/mfr/extensions/tdms/static/css/tdms.css new file mode 100644 index 000000000..9e840ee17 --- /dev/null +++ b/mfr/extensions/tdms/static/css/tdms.css @@ -0,0 +1,155 @@ +/* ---- Quadrant Grid Layout ------------------------------------------------ */ +#topLeft { + max-height: 50%; +} + +#bottomLeft { + border-top: solid #000 0.5px; +} + +#topRight { + height: 50%; + overflow: hidden; + background-color: white; +} + +#bottomRight { + height: 50%; + border-top: solid #000 0.5px; +} + +.quadrant { + display: flex; + flex-direction: column; + flex-shrink: 0; + width: 100%; + overflow: auto; + margin: 0; + padding: 0; +} + +.row { + display: flex; + flex-direction: row; +} + +.column { + display: flex; + flex-direction: column; + width: 50%; + height: 100%; + overflow: hidden; + border: solid #000 0.5px; + background-color: #F5F5F5; +} + +/* ---- Fixed Title Bar above Scrolling Content ------------------------------*/ + +.titleBar { + margin: 0; + padding: 0 1.5vw 0 1.5vw; + background-color: #FFFFFF; + border-bottom: solid #000 0.5px; +} + +.miniTitleBar { + display: flex; + flex-direction: row; + justify-content: space-between; + flex-shrink: 0; + align-items: center; + border-bottom: solid #000 0.5px; + margin: 0; + padding: 0 6vw 0 1.5vw; + height: 6vh; + cursor: pointer; + position: sticky; +} + +.belowTitle { + overflow-y: auto; + visibility: visible; +} + +/* ---- Tables -------------------------------------------------------------- */ + +table { + width: 100%; +} + +table, th, td { + border-left: 0.5px solid black; + border-bottom: 0.5px solid black; + border-collapse: collapse; + padding: 6px 0 6px 8px; + margin: 0; +} + +tr:nth-child(even) { + background: #FFFFFF; +} + +tr:nth-child(odd) { + background: #F5F5F5; +} + +th { + text-align: center; + background-color: #EBEBEB; +} + +.group { + background: #EBEBEB !important; + display: table-row; + cursor: pointer; + font-weight: bold; +} + +.channel { + background: #FFFFFF !important; + display: none; + cursor: pointer; +} + +.channel > td { + padding-left: 5%; +} + +.property { + background: #F5F5F5 !important; + display: none; + cursor: pointer; +} + +.property > td:nth-child(odd) { + padding-left: 10%; +} + +#propertyTable td { + width: 50%; +} + +/* ---- Miscellaneous ------------------------------------------------------- */ + +body { + height: 800px; +} + +#plot { + height: auto; + width: 100%; +} + +.button { + text-align: center; + font-weight: bold; + font-size: 18pt; + border-left: none; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} diff --git a/mfr/extensions/tdms/static/js/tdms.js b/mfr/extensions/tdms/static/js/tdms.js new file mode 100644 index 000000000..848180cca --- /dev/null +++ b/mfr/extensions/tdms/static/js/tdms.js @@ -0,0 +1,33 @@ +// Shows or hides content and changes button text accordingly +function showHide(contentClass, buttonId) { + var contents = document.getElementsByClassName(contentClass); + var button = document.getElementById(buttonId); + + if (button.textContent == "+") { // Show if hidden + for (var i = 0; i < contents.length; i++) { + element = contents[i] + if (element.nodeName == "TR") { + element.style.display = "table-row"; + } + else { + element.style.display = "block"; + } + } + button.textContent = "-"; + } + else { // Hide if showing + for (var i = 0; i < contents.length; i++) { + contents[i].style.display = "none"; + for (var j = 0; j < contents[i].children.length; j++) { + if (contents[i].children[j].classList.contains("button")) { + contents[i].children[j].textContent = "+"; + } + } + } + var children = document.getElementsByClassName(contentClass + "Child"); + for (var i = 0; i < children.length; i++) { + children[i].style.display = "none"; + } + button.textContent = "+"; + } +}; diff --git a/mfr/extensions/tdms/templates/viewer.mako b/mfr/extensions/tdms/templates/viewer.mako new file mode 100644 index 000000000..c4f671470 --- /dev/null +++ b/mfr/extensions/tdms/templates/viewer.mako @@ -0,0 +1,50 @@ + + + + +
    +
    +
    +

    File Contents

    +
    +
    +
    +
    File Metadata
    +

    -

    +
    +
    +
    +
      + ${fileMetadata} +
    +
    +
    +
    + + ${properties} +
    +
    +
    +
    +
    +
    +
    +

    Data Plot

    +
    + +
    +
    +
    +

    Data Table

    +
    +
    + ${table} +
    +
    +
    +
    + + + + diff --git a/requirements.txt b/requirements.txt index 88b9079c7..70ec47796 100644 --- a/requirements.txt +++ b/requirements.txt @@ -36,7 +36,7 @@ jinja2==2.10.1 mistune==0.8.1 # Pdf -reportlab==3.4.0 +reportlab==3.5.56 # Pptx # python-pptx==0.5.7 @@ -53,4 +53,12 @@ scipy==0.19.1 # Md markdown==2.6.2 + +# TDMS +npTDMS==1.1.0 + +# Issue: certifi-2015.9.6.1 and 2015.9.6.2 fail verification (https://github.com/certifi/python-certifi/issues/26) +certifi==2015.4.28 +======= certifi==2021.5.30 + diff --git a/setup.py b/setup.py index ecfe3aebd..248000eef 100755 --- a/setup.py +++ b/setup.py @@ -662,6 +662,9 @@ def parse_requirements(requirements): #'.todo.txt = mfr.extensions.codepygments:CodePygmentsRenderer', #'.vimrc' = mfr.extensions.codepygments:CodePygmentsRenderer', + #tdms + '.tdms = mfr.extensions.tdms:TdmsRenderer', + # docx # '.docx = mfr.extensions.docx:DocxRenderer', diff --git a/supportedextensions.md b/supportedextensions.md index 9c6ae1991..36fd0080f 100644 --- a/supportedextensions.md +++ b/supportedextensions.md @@ -53,6 +53,9 @@ Some file types may not be in the correct list. Please search for the file type ## PDB * .pdb +## Data +* .tdms + ## PDF * .pdf diff --git a/tests/extensions/tdms/__init__.py b/tests/extensions/tdms/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/extensions/tdms/files/invalid.tdms b/tests/extensions/tdms/files/invalid.tdms new file mode 100644 index 000000000..c59f175ed Binary files /dev/null and b/tests/extensions/tdms/files/invalid.tdms differ diff --git a/tests/extensions/tdms/files/test6.tdms b/tests/extensions/tdms/files/test6.tdms new file mode 100644 index 000000000..38addcaec Binary files /dev/null and b/tests/extensions/tdms/files/test6.tdms differ diff --git a/tests/extensions/tdms/test_renderer.py b/tests/extensions/tdms/test_renderer.py new file mode 100644 index 000000000..2ed761bb5 --- /dev/null +++ b/tests/extensions/tdms/test_renderer.py @@ -0,0 +1,65 @@ +import os +import pytest + +from bs4 import BeautifulSoup + +from mfr.core.provider import ProviderMetadata +from mfr.extensions.tdms import TdmsRenderer + + +@pytest.fixture +def metadata(): + return ProviderMetadata('test8', '.tdms', 'text/plain', '1234', 'http://wb.osf.io/file/test6.tdms?token=1234') + + +@pytest.fixture +def test_tdms_file_path(): + return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files', 'test6.tdms') + + +@pytest.fixture +def invalid_tdms_file_path(): + return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files', 'invalid.tdms') + +@pytest.fixture +def url(): + return 'http://osf.io/file/test6.tdms' + + +@pytest.fixture +def assets_url(): + return 'http://mfr.osf.io/assets' + + +@pytest.fixture +def export_url(): + return 'http://mfr.osf.io/export?url=' + url() + + +@pytest.fixture +def renderer(metadata, test_tdms_file_path, url, assets_url, export_url): + return TdmsRenderer(metadata, test_tdms_file_path, url, assets_url, export_url) + + +class TestTdmsRenderer: + + def test_render_tdms_file_required(self, renderer): + assert renderer.file_required is True + + def test_render_tdms_cache_result(self, renderer): + assert renderer.cache_result is True + + def test_render_tdms(self, test_tdms_file_path, assets_url, export_url): + metadata = ProviderMetadata('test6', '.tdms', 'text/plain', '1234', 'http://wb.osf.io/file/test6.tdms?token=1234') + renderer = TdmsRenderer(metadata, test_tdms_file_path, url, assets_url, export_url) + body = renderer.render() + name = "sp0006_042419" + startTime = "wf_start_time: 2019-04-24T18:17:11.234133" + startFrequency = "4E+05" + endFrequency = "5E+05" + plot = """data:image/png;base64,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ICBJllYXTJgwQXfccYfmzZunl19+WaNGjTptD05pURi+7777NHz4cGVkZGjXrl2aPXu2IiMjdeuttyo+Pl7jx49Xfn6+OnfuLLfbrXvuuUc5OTm67LLLJEnDhg1T3759dccdd2ju3LkqKyvTQw89pMmTJ5sh9K677tKCBQs0ffp0jRs3Th988IGWLFmiZcuWmfPIz8/X6NGjNWjQIA0ePFjz58/XoUOHNHbsWEmyNBcAAHBihmHou8oaleyo1Lod+7Vue6W2lB9Qc8txnVERykxL0KCMRGVnJGpgeqISO8ac+IBzyPmdGl8Ma8vvO3DgQP3f//2fevTooaioxnHvP/7jPxQdHa1PP/1U6enpkqTKykp98803IVdozz//fO3evdt8/a9//UuHDx8OeZ/Fixera9eucrvdJzXX/v37KxAIaNWqVY2aKARdd9116tixo1588UUtX75cH3300Um9V1NaFIa/++473Xrrrdq3b5/OP/98/fjHP9aaNWvM5D9v3jxFRERo5MiR8vl8ys3N1QsvvGAeHxkZqXfeeUeTJk1STk6OOnbsqNGjR+uxxx4zx/Ts2VPLli3TtGnT9Nxzz6l79+5auHCheWekJI0aNUp79uxRQUGBysrKlJWVpeXLl4fcVBduLgAA4Bj/kYA27arWuu379dm2fdrwnUcVYZYNdO3k1KAeicrO6KzsjET1TXErJqptPty2JUsV2oLJkyfr97//vW699VZNnz5dnTt31rfffqs33nhDCxcuVFxcnMaPH6/7779fXbp0UdeuXTVr1ixFRIR+/376059qwYIFysnJUV1dnWbMmBFylff222/XU089peuvv16PPfaYunfvrh07dugvf/mLpk+fbmkNco8ePTR69GiNGzfOvIFux44dqqio0M033yypPkOOGTNGM2fO1IUXXtjs3/S3VIvC8BtvvNHsfpfLpeeff17PP//8CcdkZGQ0WgZxvCuuuEKff/55s2OmTJliLos42bkAAGBX+w/5tX5HpdbtqNT6HZXa+F2VfEcCJxwf4ZB6J7vNq77ZGYnqnhjLkpNzVGpqqlavXq0ZM2Zo2LBh8vl8ysjI0DXXXGMG3qeeespcTtGpUyf98pe/VHV1dch5nnnmGY0dO1Y/+clPlJqaqueee04lJSXm/g4dOuijjz7SjBkzdOONN+rAgQPq1q2brrrqqhZd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""" + + assert plot in body + assert startTime in body + assert startFrequency in body + assert endFrequency in body diff --git a/travis-config.json b/travis-config.json deleted file mode 100644 index 9e26dfeeb..000000000 --- a/travis-config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file