Feldspar is an integration mechanism for building data donation applications that can be hosted on the Next platform. It enables researchers to create custom data extraction and donation flows using Python and React.
More information about the Port program can be found here.
Feldspar enables researchers to:
- Extract only the data of interest through local processing (on the participant's device) using Python (Pyodide)
- Prompt participants for questions about the data
- Enable participants to inspect the extracted data before donation
- Enable participants to delete table rows before donation
- Consent or decline to donate the extracted data
- Fork or clone this repo
- Install Node.js
- Install Python (Version 3.11 or higher)
- Install Poetry
- Install Earthly CLI
-
Install dependencies:
npm install
-
Run the project locally with hot reloading (builds Python package and starts the development server):
npm run start
-
Access the application at http://localhost:3000
The core of Feldspar's functionality is in the Python script at packages/python/port/script.py
. This script defines the flow of the data donation process.
- Fork the repository to create your own version
- Navigate to
packages/python/port/script.py
- Modify the
process(sessionId)
function to customize your data donation flow
A basic donation flow typically includes:
- Prompt the participant to select a file
- Extract relevant data from the file
- Present the extracted data in a consent form
- Process the participant's consent decision
def prompt_file(extensions):
description = props.Translatable({
"en": "Please select your data export file.",
"de": "Bitte wählen Sie Ihre Datenexportdatei aus.",
"it": "Seleziona il tuo file di esportazione dati.",
"nl": "Selecteer uw data-exportbestand."
})
return props.PropsUIPromptFileInput(description, extensions)
Add any static assets your script needs to packages/python/port/assets/
. Access them in your script:
from port.api.assets import *
def process(sessionId):
# Path to an asset
path = asset_path("my_file.txt")
# Open an asset directly
file = open_asset("my_file.txt")
# Read asset contents
content = read_asset("my_file.txt")
If you need additional Python packages, add them to packages/python/pyproject.toml
in the tool.poetry.dependencies
section.
Feldspar allows you to add custom UI components that can be used in your Python script. This is a more advanced feature that requires understanding both Python and React.
Create a new folder in packages/data-collector/src/components/my_component/
and add a types.ts
file:
export interface PropsUIPromptMyComponent {
__type__: "PropsUIPromptMyComponent";
title: string;
// Add any other properties your component needs
}
Add a component.tsx
file to implement your component:
import React from "react";
import { PropsUIPromptMyComponent } from "./types";
import { ReactFactoryContext } from "@eyra/feldspar";
type Props = PropsUIPromptMyComponent & ReactFactoryContext;
export const MyComponent: React.FC<Props> = ({ title, resolve }) => {
return (
<div>
<h1>{title}</h1>
<button
onClick={() => resolve?.({ __type__: "PayloadTrue", value: true })}
>
Continue
</button>
</div>
);
};
Add a new file at packages/data-collector/src/factories/my_component.tsx
:
import { PromptFactory, ReactFactoryContext } from "@eyra/feldspar";
import React from "react";
import { MyComponent } from "../components/my_component/component";
import { PropsUIPromptMyComponent } from "../components/my_component/types";
export class MyComponentFactory implements PromptFactory {
create(body: unknown, context: ReactFactoryContext) {
if (this.isMyComponent(body)) {
return <MyComponent {...body} {...context} />;
}
return null;
}
private isMyComponent(body: unknown): body is PropsUIPromptMyComponent {
return (
(body as PropsUIPromptMyComponent).__type__ === "PropsUIPromptMyComponent"
);
}
}
Update packages/data-collector/src/App.tsx
to include your new factory:
import { DataSubmissionPageFactory, ScriptHostComponent } from "@eyra/feldspar";
import { HelloWorldFactory } from "./factories/hello_world";
import { MyComponentFactory } from "./factories/my_component";
function App() {
return (
<div className="App">
<ScriptHostComponent
workerUrl="./py_worker.js"
standalone={process.env.NODE_ENV !== "production"}
factories={[
new DataSubmissionPageFactory({
promptFactories: [
new HelloWorldFactory(),
new MyComponentFactory(), // Add your new factory here
],
}),
]}
/>
</div>
);
}
export default App;
Add a function to your script.py
to create your component:
def prompt_my_component(title):
return {
"__type__": "PropsUIPromptMyComponent",
"title": title
}
def process(sessionId):
result = yield render_data_submission_page(prompt_my_component("My Custom Component"))
# Handle the result...
When your data donation application is ready for deployment:
-
Create a release package:
./release.sh
-
Find the generated ZIP file in the
releases/
directory, named with the current date and sequential number (e.g.,feldspar_2023-07-15_1.zip
) -
This ZIP file can be deployed to:
- The Next platform
- A self-hosted environment
- Any server that can host static files and store the donated data
To use the release in the Next platform, add a "Donate task" and select the generated ZIP file as the "Flow application".
Feldspar is part of the Port program for data donation and has been funded by the UU, PDI-SSH (D3i project), and Eyra.
We welcome contributions to make Feldspar better. Please read our contributing guidelines for details on how to submit issues, feature requests, and pull requests.