Skip to content

Commit

Permalink
Merge pull request #526 from hotosm/feature/dc-stats
Browse files Browse the repository at this point in the history
Feature : Data completeness stats
  • Loading branch information
kshitijrajsharma committed Feb 7, 2024
2 parents c1b9154 + da80e3b commit ac17c36
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 13 deletions.
2 changes: 1 addition & 1 deletion docs/setup-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Most of these environment variables have reasonable default settings.
- `GARMIN_CONFIG`, `GARMIN_MKGMAP` absolute paths to garmin JARs
- `OVERPASS_API_URL` url of Overpass api endpoint

- `RAW_DATA_API_URL` url of Galaxy api endpoint
- `RAW_DATA_API_URL` url of Raw data api endpoint

- `DATABASE_URL` Database URL. Defaults to `postgres:///exports`
- `DEBUG` Whether to enable debug mode. Defaults to `False` (production).
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mercantile~=0.10.0
psycopg2
python3-openid==3.2.0
social-auth-app-django==5.4.0
social-auth-core==4.4.2 ### Upgrade this to include oauth2
social-auth-core @ git+https://github.com/kshitijrajsharma/social-core.git ### Upgrade this to include osm oauth2 when released
pytz
pyyaml>=5.3
raven
Expand Down
4 changes: 2 additions & 2 deletions ui/app/actions/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ export const getOverpassTimestamp = () => (dispatch, getState) => {

export const getGalaxyTimestamp = () => (dispatch, getState) => {
return axios({
baseURL: "https://api-prod.raw-data.hotosm.org/v1",
url: "/status/"
baseURL: window.RAW_DATA_API_URL,
url: "v1/status/"
})
.then(response =>
dispatch({
Expand Down
4 changes: 4 additions & 0 deletions ui/app/actions/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ if (window.location.port) {
hostname += `:${window.location.port}`;
}

if (window.RAW_DATA_API_URL == null) {
window.RAW_DATA_API_URL = process.env.RAW_DATA_API_URL;
}

if (window.EXPORTS_API_URL == null) {
window.EXPORTS_API_URL = process.env.EXPORTS_API_URL;

Expand Down
2 changes: 2 additions & 0 deletions ui/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Home from "./components/Home";
import NavBar from "./components/NavBar";
import Stats from "./components/Stats";
import Banner from "./components/Banner";
import Message from "./components/Message.js";
import { requireAuth } from "./components/utils";

import "@blueprintjs/core/dist/blueprint.css";
Expand All @@ -49,6 +50,7 @@ export default ({ history }) => {
<div style={{ height: "100%" }}>
<Auth />
<NavBar />
<Message/>
<Banner />
<Switch>
<Route path="/authorized" component={Authorized} />
Expand Down
85 changes: 83 additions & 2 deletions ui/app/components/ExportForm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import area from "@turf/area";
import axios from "axios";
import bbox from "@turf/bbox";
import React, { Component } from "react";
import { Col, Nav, Panel, Row } from "react-bootstrap";
Expand All @@ -8,7 +9,6 @@ import { Redirect, Route, Switch } from "react-router";
import { NavLink } from "react-router-dom";
import { Fields, formValueSelector, reduxForm } from "redux-form";
import { pointToTile } from "tilebelt";

import ChooseFormats from "./ChooseFormats";
import DescribeExport from "./DescribeExport";
import ExportAOIField from "./ExportAOIField";
Expand Down Expand Up @@ -128,6 +128,86 @@ export class ExportForm extends Component {
};
}

async fetchData(geometry) {
const url = window.RAW_DATA_API_URL + "v1/stats/polygon/";
try {
const response = await axios.post(url, {
geometry: geometry
}, {
headers: {"Content-Type": "application/json"}
});

if (response.data) {

this.setState({ fetchedInfo: response.data });
}
} catch (error) {
console.error("Failed to fetch summary data", error);

}
}

componentDidUpdate(prevProps) {
if (this.props.formValues.the_geom !== prevProps.formValues.the_geom) {
this.fetchData(this.props.formValues.the_geom);
}
}

renderFetchedInfo() {
const { fetchedInfo } = this.state;
if (!this.props.formValues.the_geom) return null;
if (!fetchedInfo) return null;

// Function to trigger the download of the raw data as a JSON file
const downloadRawData = () => {
const filename = "raw_region_summary.json";
const jsonStr = JSON.stringify(fetchedInfo, null, 4);
const element = document.createElement('a');
element.setAttribute('href', 'data:text/json;charset=utf-8,' + encodeURIComponent(jsonStr));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};

return (
<Panel style={{ marginTop: "10px" }}>
<div>
<div>
<strong style={{ fontSize: "smaller" }}>Buildings:</strong>
<p style={{ fontSize: "smaller", textAlign: "justify", margin: "10px 0" }}>
<FormattedMessage
id="export.dc.stats.buildings"
defaultMessage="{buildings}"
values={{ buildings: fetchedInfo.summary.buildings }}
/>
</p>
</div>
<div>
<strong style={{ fontSize: "smaller" }}>Roads:</strong>
<p style={{ fontSize: "smaller", textAlign: "justify", margin: "10px 0" }}>
<FormattedMessage
id="export.dc.stats.roads"
defaultMessage="{roads}"
values={{ roads: fetchedInfo.summary.roads }}
/>
</p>
</div>
<div style={{ fontSize: "smaller", marginTop: "10px" }}>
More info:
<a href="#" onClick={downloadRawData} style={{ marginLeft: "5px" }}>Download</a>,
<a href={fetchedInfo.meta.indicators} target="_blank" rel="noopener noreferrer" style={{ marginLeft: "5px" }}>Indicators</a>,
<a href={fetchedInfo.meta.metrics} target="_blank" rel="noopener noreferrer" style={{ marginLeft: "5px" }}>Metrics</a>
</div>
</div>
</Panel>
);
}




componentWillMount() {
const { getConfigurations, getOverpassTimestamp, getGalaxyTimestamp} = this.props;

Expand Down Expand Up @@ -281,10 +361,11 @@ export class ExportForm extends Component {
/>}
/>
</Switch>
{this.renderFetchedInfo()}
<Panel style={{ marginTop: "20px" }}>
<FormattedMessage
id="ui.overpass_last_updated"
defaultMessage="Img/pbf/obf/ updated {overpassLastUpdated}, Rest of other formats updated {galaxyLastUpdated} "
defaultMessage="Img/pbf/obf updated {overpassLastUpdated}, Rest of other formats updated {galaxyLastUpdated} "
values={{ overpassLastUpdated, galaxyLastUpdated }}
/>
</Panel>
Expand Down
34 changes: 34 additions & 0 deletions ui/app/components/Message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { Component } from "react";

class Message extends Component {
constructor(props) {
super(props);
// Use "messageClosed" as the key to check if the message was previously closed
const messageClosed = localStorage.getItem("messageClosed") === "true";
this.state = {
isVisible: !messageClosed,
};
}

handleClose = () => {
this.setState({ isVisible: false });
// When closing, set "messageClosed" in localStorage to "true"
localStorage.setItem("messageClosed", "true");
};

render() {
if (!this.state.isVisible) {
return null;
}
return (
<div className="banner" style={{ backgroundColor: "#ffcc00", color: "black", textAlign: "center", padding: "10px", position: "relative" }}>
<p>We have recently upgraded from OAuth 1.0 to 2.0. Please Logout and Login again before use!</p>
<button onClick={this.handleClose} style={{ position: "absolute", top: "5px", right: "10px", cursor: "pointer" }}>
×
</button>
</div>
);
}
}

export default Message;
10 changes: 5 additions & 5 deletions ui/app/components/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export const AVAILABLE_EXPORT_FORMATS = {
SQL <code>.sql</code>
</span>
),
mbtiles: (
<span key="mbtiles">
MBTiles <code>.mbtiles</code>
</span>
),
garmin_img: (
<span key="garmin_img">
Garmin <code>.img</code>
Expand All @@ -83,11 +88,6 @@ export const AVAILABLE_EXPORT_FORMATS = {
OsmAnd <code>.obf</code>
</span>
),
mbtiles: (
<span key="osmand_obf">
MBTiles <code>.mbtiles</code>
</span>
),
bundle: (
<span key="bundle">
<a href="http://posm.io/">POSM</a> bundle
Expand Down
1 change: 1 addition & 0 deletions ui/templates/ui/v3.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<script>
var EXPORTS_API_URL = "{{ request.scheme }}://{{ request.get_host }}";
var OAUTH_CLIENT_ID = "{{ client_id }}";
var RAW_DATA_API_URL = "{{ RAW_DATA_API_URL }}";
</script>
<script src="{% static 'ui/js/bundle.js' %}"></script>
</body>
Expand Down
4 changes: 3 additions & 1 deletion ui/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def v3(request, *args, **kwargs):
skip_authorization=True,
)

context = dict(client_id=ui_app.client_id)
context = dict(
client_id=ui_app.client_id, RAW_DATA_API_URL=settings.RAW_DATA_API_URL
)
if settings.MATOMO_URL is not None and settings.MATOMO_SITEID is not None:
context.update(
{"MATOMO_URL": settings.MATOMO_URL, "MATOMO_SITEID": settings.MATOMO_SITEID}
Expand Down
4 changes: 3 additions & 1 deletion ui/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ const config = {
plugins: [new webpack.DefinePlugin({
"process.env": {
CLIENT_ID: JSON.stringify(process.env.CLIENT_ID),
EXPORTS_API_URL: JSON.stringify(process.env.EXPORTS_API_URL)
EXPORTS_API_URL: JSON.stringify(process.env.EXPORTS_API_URL),
RAW_DATA_API_URL: JSON.stringify(process.env.RAW_DATA_API_URL)
}
}), new webpack.NamedModulesPlugin(), new WriteFilePlugin()],
resolve: {
Expand All @@ -110,6 +111,7 @@ if (process.env.NODE_ENV === "production") {
"process.env": {
CLIENT_ID: JSON.stringify(process.env.CLIENT_ID),
EXPORTS_API_URL: JSON.stringify(process.env.EXPORTS_API_URL),
RAW_DATA_API_URL: JSON.stringify(process.env.RAW_DATA_API_URL),
NODE_ENV: JSON.stringify("production")
}
}),
Expand Down

0 comments on commit ac17c36

Please sign in to comment.