Skip to content

Commit

Permalink
feat(update): add validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
stdavis committed Jun 5, 2023
1 parent 0221929 commit c6e79ce
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 22 deletions.
27 changes: 25 additions & 2 deletions apps-scripts/Code.gs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const FUNCTION_URL =
'https://<project-id>.cloudfunctions.net/updateRemoteConfigFromSheets';
'https://us-central1-ut-dts-agrc-deq-enviro-dev.cloudfunctions.net/configs';

function onOpen() {
const ui = SpreadsheetApp.getUi();
Expand All @@ -19,7 +19,30 @@ function callUpdateFunction() {
};
// call the server
const response = UrlFetchApp.fetch(FUNCTION_URL, options);
const responseJson = JSON.parse(response.getContentText());

const ui = SpreadsheetApp.getUi();
ui.alert('Deploy Result', response.getContentText().trim(), ui.ButtonSet.OK);

var title = 'Deploy Successful';
var htmlOutput = HtmlService.createHtmlOutput(
'<div style="font-family: sans-serif">'
);
if (responseJson.success) {
htmlOutput.append(`<h4>${responseJson.message}</h4>`);
} else {
title = 'Deploy Unsuccessful';
htmlOutput.append(
`<h4>There was an error with the deployment:</h4><pre>${responseJson.error}</pre>`
);
}

if (responseJson.validationErrors.length > 0) {
htmlOutput.append('<h4>Validation Errors</h4>');
for (var message of responseJson.validationErrors) {
htmlOutput.append(`<p>${message}</p>`);
}
}
htmlOutput.append('</div>');

ui.showModalDialog(htmlOutput, title);
}
15 changes: 1 addition & 14 deletions functions/common/config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { array, object, string } from 'yup';
import { array, string } from 'yup';

export const fieldConfigs = {
queryLayers: {
Expand Down Expand Up @@ -162,19 +162,6 @@ export const fieldKeys = {
relatedTables: getFieldKeys(fieldConfigs.relatedTables),
};

function getSchema(fieldConfigs) {
return Object.keys(fieldConfigs).reduce((obj, key) => {
obj[fieldConfigs[key].name] = fieldConfigs[key].schema;

return obj;
}, {});
}

export const schemas = {
queryLayers: object(getSchema(fieldConfigs.queryLayers)),
relatedTables: object(getSchema(fieldConfigs.relatedTables)),
};

export const downloadFormats = {
csv: 'csv',
excel: 'excel',
Expand Down
19 changes: 19 additions & 0 deletions functions/common/validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { object } from 'yup';
import { fieldConfigs } from './config.js';

function getSchema(fieldConfigs) {
return Object.keys(fieldConfigs).reduce((obj, key) => {
obj[fieldConfigs[key].name] = fieldConfigs[key].schema;

return obj;
}, {});
}

export const schemas = {
queryLayers: object(getSchema(fieldConfigs.queryLayers)),
relatedTables: object(getSchema(fieldConfigs.relatedTables)),
};

export function supportsExport(serviceJSON) {
return /extract/i.test(serviceJSON.capabilities);
}
44 changes: 42 additions & 2 deletions functions/update.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
import admin from 'firebase-admin';
import { google } from 'googleapis';
import got from 'got';
import auth from './common/auth.js';
import { fieldConfigs, fieldKeys, fieldNames } from './common/config.js';
import { schemas, supportsExport } from './common/validation.js';

const secretsClient = new SecretManagerServiceClient();

Expand Down Expand Up @@ -85,6 +87,33 @@ export function checkForDuplicateIds(configs) {
}
}

async function validateQueryLayers(queryLayers) {
const validationErrors = [];

for (const queryLayer of JSON.parse(queryLayers)) {
const layerName = queryLayer[fieldNames.queryLayers.layerName];
try {
schemas.queryLayers.validateSync(queryLayer);
} catch (error) {
validationErrors.push(
`${layerName}: schema validation error: ${error.message}`
);
}
if (queryLayer[fieldNames.queryLayers.featureService]) {
const serviceURL = queryLayer[fieldNames.queryLayers.featureService];
const serviceJSON = await got(`${serviceURL}?f=json`).json();

if (!supportsExport(serviceJSON)) {
validationErrors.push(
`${layerName}: feature service does not support export/downloading!`
);
}
}
}

return validationErrors;
}

async function updateRemoteConfigs(queryLayers, relatedTables) {
const remoteConfig = admin.remoteConfig();

Expand All @@ -102,18 +131,29 @@ async function updateRemoteConfigs(queryLayers, relatedTables) {
console.log('validating new template');
await remoteConfig.validateTemplate(template);

const validationErrors = await validateQueryLayers(queryLayers);

if (
originalValues.queryLayers === queryLayers &&
originalValues.relatedTables === relatedTables
) {
return 'No changes detected between the config spreadsheet and app configs.';
return {
success: true,
message:
'No changes detected between the config spreadsheet and app configs.',
validationErrors,
};
}

console.log('publishing updated template');
const updatedTemplate = await remoteConfig.publishTemplate(template);
console.log('ETag from server: ' + updatedTemplate.etag);

return 'App configs updated successfully!';
return {
success: true,
message: 'App configs updated successfully!',
validationErrors,
};
}

export default async function main() {
Expand Down
4 changes: 1 addition & 3 deletions src/components/Map.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,7 @@ export default function MapComponent() {
const { count, extent } = await layerView.queryExtent();
const featureSet = await layerView.queryFeatures();

const supportsExport = /extract/i.test(
featureLayer.sourceJSON.capabilities
);
const supportsExport = supportsExport(featureLayer.sourceJSON);
if (!supportsExport) {
console.warn('Layer does not support exporting', layer);
}
Expand Down
3 changes: 2 additions & 1 deletion src/components/search-wizard/Wizard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { useMutation } from '@tanstack/react-query';
import ky from 'ky';
import { useEffect, useState } from 'react';
import { useRemoteConfigString } from 'reactfire';
import { fieldNames, schemas } from '../../../functions/common/config.js';
import { fieldNames } from '../../../functions/common/config.js';
import { schemas } from '../../../functions/common/validation.js';
import { useSearchMachine } from '../../SearchMachineProvider.jsx';
import Button from '../../utah-design-system/Button.jsx';
import AdvancedFilter from './AdvancedFilter.jsx';
Expand Down

0 comments on commit c6e79ce

Please sign in to comment.