Service worker not creating networkFirst Cache - caching

My Service Worker:
importScripts('https://storage.googleapis.com/workbox-
cdn/releases/3.0.0/workbox-sw.js');
//Use Workbox Precache for our static Assets
workbox.precaching.precacheAndRoute([]);
console.log('this is my custom service worker');
//Create articles Cache from online resources
const onlineResources = workbox.strategies.networkFirst({
cacheName: 'articles-cache',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 50,
}),
],
});
workbox.routing.registerRoute('https://newsapi.org/(.*)', args => {
return onlineResources.handle(args);
});
The precache cache works but the onlineResources Cache is never created.
A look at my file structure:
So I don't think scope is an issue even though I cant see clients in my service worker on Chrome dev tools.
Lastly here is my app.js file:
//main populates main tags in indexpage
const main = document.querySelector('main');
//this populates the source dropdown menu with sources
const sourceSelector = document.querySelector('#sourceSelector');
//set default source so page loads this
const defaultSource = 'bbc-news';
//on window load call update news and when update
window.addEventListener('load', async e => {
updateNews();
await updateSources();
sourceSelector.value = defaultSource;
//when sourceSelector is changed update the news with the new source
sourceSelector.addEventListener('change',e =>{
updateNews(e.target.value);
});
//checks for serviceWorker in browser
if('serviceWorker'in navigator){
try{
//if there is register it from a path
navigator.serviceWorker.register('/sw.js');
console.log('registered!');
} catch(error){
console.log('no register!',error);
}
}
});
async function updateNews(source= defaultSource){
//response awaits a fetch of the news API call
const res = await fetch(`https://newsapi.org/v2/top-headlines?sources=${source}&apiKey=82b0c1e5744542bdb8c02b61d6499d8f`);
const json = await res.json();
//fill the html with the retrieved json articles
main.innerHTML = json.articles.map(createArticle).join('\n');
}
//Update the news source
async function updateSources(){
const res = await fetch(`https://newsapi.org/v2/sources?apiKey=82b0c1e5744542bdb8c02b61d6499d8f`);
const json = await res.json();
//Whatever source the sourceSelector picks gets mapped and we take the id of it - It is used with updateNews();
sourceSelector.innerHTML = json.sources
.map(src=>`<option value="${src.id}">${src.name}</option>`).join('\n');
}
function createArticle(article){
return ` <div class="article">
<a href="${article.url}">
<h2>${article.title}</h2>
<img src="${article.urlToImage}">
<p>${article.description}</p>
</a>
</div>
`;
}
App.js plugs into newsAPI and outputs the JSON to the pages HTML.

When you register the route you seem to be trying to use a regex as a string. I think it is literally interpreting the route as a string that includes .*. Instead try the regex /^https:\/\/newsapi\.org/ which per the docs will match from the beginning of the url.

Related

Pre-scan web page for dynamic tests

Looking for a definitive answer to the question posed by #JeffTanner here about generating dynamic tests. From that question and the Cypress samples, it's clear that we need to know the number of tests required before generating them.
Problem
We have a web page containing a table of Healthcare analytic data that is refreshed many times during the day. Each refresh the team must check the data, and to divvy up the work we run each row as a separate test. But the number of rows varies every time which means I must count the rows and update the system on each run. Looking for a way to programmatically get the row count.
The HTML is a table of <tbody><tr></tr></tbody>, so the following is enough to get the count but I can't run it in a beforeEach(), the error thrown is "No tests found"
let rowCount;
beforeEach(() => {
cy.visit('/analytics')
cy.get('tbody tr').then($els => rowCount = $els.length)
})
Cypress._.times(rowCount => {
it('process row', () => {
...
})
})
The before:run event fires before the tests start, you can scan the web page there.
Set the event listener in setupNodeEvents(). Cypress commands won't run here, but you can use equivalent Node commands.
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('before:run', async (details) => {
try {
const fetch = require('node-fetch');
const fs = require('fs-extra');
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const response = await fetch(config.env.prescan); // default below
const body = await response.text(); // or pass in command line
const dom = new JSDOM(body);
const rows = dom.window.document.body.querySelectorAll('tr') // query
// save results
fs.writeJson('./cypress/fixtures/analytics-rows.json', {rows:rows.length})
} catch (error) {
console.log('error:', error)
}
})
},
},
env: {
prefetch: 'url-for-analytics-page'
}
})
Test
import {rows} from './cypress/fixtures/analytics-rows.json' // read row count
Cypress._.times(rows, (row) => {
it(`tests row ${row}`, () => {
...
})
}
You can add a script scan-for-rows.js to the project scripts folder, like this
const rp = require('request-promise');
const $ = require('cheerio');
const fs = require('fs-extra');
rp('my-url')
.then(function(html) {
const rowCount = $('big > a', html).length
fs.writeJson('row-count.json', {rowCount})
})
.catch(function(err){
//handle error
});
Then in package.json call a pre-test script every time a new version of the web page appears.
One possibility is to run the above Cypress test in a pretest script which will always run before your main test script.
// package.json
{
...
"scripts": {
"pretest": "npx cypress run --spec cypress/e2e/pre-scan.cy.js",
"test": "npx cypress run --spec cypress/e2e/main-test.cy.js",
}
}
// pre-scan.cy.js
it('scans for table row count', () => {
cy.visit('/analytics');
cy.get('tbody tr').then($els => {
const rowCount = $els.length;
cy.writeFile('cypress/fixtures/rowcount.json', rowCount);
});
});
Here's a way to get the row count in the spec file without using extra packages, plugins, test hooks, or npm scripts.
Basically, you can create a separate module that makes a synchronous HTTP request using the XMLHTTPRequest class to the /analytics endpoint and use the browser's DOMParser class to find the return the number of <tr> tags.
// scripts/get-row-count.js
export function getRowCount() {
let request = new XMLHttpRequest();
// Set async to false because Cypress will not wait for async functions to finish before looking for it() statements
request.open('GET', '/analytics', false);
request.send(null);
const document = new DOMParser().parseFromString(request.response, 'text/html');
const trTags = Array.from(document.getElementsByTagName('tr'));
return trTags.length;
};
Then in the spec file, import the new function and now you can get an updated row count whenever you need it.
import { getRowCount } from '../scripts/get-row-count';
Cypress._.times(getRowCount() => {
it('process row', () => {
...
})
})
The reason for XMLHTTPRequest instead of fetch is because it allows synchronous requests to be made. Synchronous requests are needed because Cypress won't wait for async requests to come back before parsing for it() blocks.
With this, you always have the most up to date row count without it going stale.

Strapi V4 how to modify data response for a component (dynamic zone)

Imagine a case where an editor adds a “Latest Products” component to a page using dynamic zone: they add a title, a summary, and then the latest products will automatically be fetched to be available in the response. How can I add this data to the response of the component?
I know we can override the response for content types using a custom controller, but I can't find anything for how to modify the response for a component.
Maybe there's an alternative approach I haven't thought of, but coming from a Drupal preprocess-everything background this is all I can think of.
Any help appreciated!
I'm sure this isn't the best way, but I created a service for components that can be used in the content type controller to modify the response. Any improvements appreciated!
/api/custom-page/controllers/custom-page.js
'use strict';
/**
* custom-page controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::custom-page.custom-page', ({ strapi }) => ({
async find(ctx) {
const componentService = strapi.service('api::components.components');
let { data, meta } = await super.find(ctx);
data = await Promise.all(data.map(async (entry, index) => {
if(entry.attributes.sections){
await Promise.all(entry.attributes.sections.map(async (section, index) => {
const component = await componentService.getComponent(section);
entry.attributes.sections[index] = component;
}));
}
return entry;
}));
return { data, meta };
},
}));
/components/services/components.js
'use strict';
/**
* components service.
*/
module.exports = () => ({
getComponent: async (input) => {
// Latest products
if(input.__component === 'sections.latest-products'){
input.products = 'customdatahere';
}
return input;
}
});

Shared object between loader & action does not update after action is called in Remix.run?

I have a mock database for playing around with some loaders and actions.
Here's the rough layout:
const db = { key: "bar" }
export const action = async ({ request }) => {
db.key = "foo"
}
export const loader = async ({ request }) => {
return json(db)
}
I have an issue though. When the action is called, it successfully updates db.key, however, the loader is called afterwards & the value is {key: "bar" }. Does anyone know why the object is not updated when the loader is called again?
I think that there is something related to "Data Writes" topic.
Whenever a form is submitted it reload all of the data for all of the routes on the page. Thats why many people reach for global state management libraries like redux.
I don't know if it helps but here I got a simple way where you can send a post request, update it with the action and load the content searching at the URL with the loader.
export const action: ActionFunction = async ({ request }) => {
const form = await request.formData();
const key = form.get("key");
return redirect(`/?key=${key}_updated`);
};
export const loader: LoaderFunction = async ({ request }: any) => {
const url = new URL(request.url);
const key = url.searchParams.get("key");
return json({ key });
};
export default function Index() {
const loaderData = useLoaderData();
return (
<div>
<span>loaderData: {loaderData.key}</span>
<Form method="post">
<input type="text" id="key" name="key" defaultValue={loaderData.key} />
<button>Submit</button>
</Form>
</div>
);
}

Update the cache of Apollo client 3 when polling not working

I am playing with the cache of #apollo/client v3. Here's the codesandbox.
I am adding a user to a cached list of users using client.writeQuery, and the query has a pollInterval to refetch every few seconds.
I am able to add the user to the list, it does refresh the UI, and I can see the pollInterval working in the network tab of Chrome.
THE PROBLEM
I would expect the list of users to return to its initial state when the polling kicks in, and overwrite the user I added manually to the cache, but it does not.
Apollo config
export const cache = new InMemoryCache();
const client = new ApolloClient({
cache,
link: new HttpLink({
uri: "https://fakeql.com/graphql/218375d695835e0850a14a3c505a6447"
})
});
UserList
export const UserList = () => {
const { optimisticAddUserToCache, data, loading } = useUserList();
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<button onClick={() => optimisticAddUserToCache()}>Add User to cache</button>
<ol>
{data?.users.map(user => {
return <li key={user.id}>{user.firstname}</li>;
})}
</ol>
</div>
);
}
useUserList
const GET_USER_LIST = gql`
query Users {
users {
id
firstname
}
}
`;
export const useUserList = () => {
const { loading, error, data, refetch } = useQuery(GET_USER_LIST, {
pollInterval: 4000 // It does poll (check chromes's network tab), but it doesn't seem to overwrite the cache
});
const client = useApolloClient();
const optimisticAddUserToCache = () => {
const newUser: any = {
id: `userId-${Math.random()}`,
firstname: "JOHN DOE",
__typename: "User"
};
const currentUserList = client.readQuery({ query: GET_USER_LIST }).users;
// This works, it does add a user, and UI refreshes.
client.writeQuery({
query: GET_USER_LIST,
data: {
users: [newUser, ...currentUserList]
}
});
};
return { optimisticAddUserToCache, loading, error, data, refetch };
};
Working as expected (almost)
Polled response arrives always with the same data ...
... doesn't result in write to cache (no content compared) ...
... no data change in cache ...
... data property (from useQuery) not updated ...
... no data updated, no component rerendering.
For optimistic update working you need a real mutation, real [persisted] change on remote datasource ... propagated to next polled responses.

Vue js function countSubcategories() returns [object Promise]

countSubcategories() function returns [object Promise] where it should return row counts of mapped subcategories.
This code is in vue.js & Laravel, Any suggestions on this?
<div v-for="(cat,index) in cats.data" :key="cat.id">
{{ countSubcategories(cat.id) }} // Here subcategories row counts should be displayed.
</div>
<script>
export default {
data() {
return {
cats: {},
childcounts: ""
};
},
created() {
this.getCategories();
},
methods: {
countSubcategories(id) {
return axios
.get("/api/user-permission-child-count/" + `${id}`)
.then(response => {
this.childcounts = response.data;
return response.data;
});
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => (this.cats = response.data));
}
}
};
</script>
As Aron stated in the previous answer as you are calling direct from the template the information is not ready when the template is rendered.
As far as I understood you need to run getCategories first so then you can fetch the rest of your data, right?
If that's the case I have a suggestion:
Send an array of cat ids to your back-end and there you could send back the list of subcategories you need, this and this one are good resources so read.
And instead of having 2 getCategories and countSubcategories you could "merge" then like this:
fetchCategoriesAndSubcategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => {
this.cats = response.data;
let catIds = this.cats.map(cat => (cat.id));
return this.countSubcategories(catIds) // dont forget to change your REST endpoint to manage receiving an array of ids
})
.then(response => {
this.childcounts = response.data
});
}
Promises allow you to return promises within and chain .then methods
So in your created() you could just call this.fetchCategoriesAndSubcategories passing the data you need. Also you can update your template by adding a v-if so it doesn't throw an error while the promise didn't finish loading. something like this:
<div v-if="childCounts" v-for="(subcategorie, index) in childCounts" :key="subcategorie.id">
{{ subcategorie }} // Here subcategories row counts should be displayed.
</div>
Hello!
Based on the provided information, it could be 2 things. First of all, you may try replacing:
return response.data;
with:
console.log(this.childcounts)
and look in the console if you have the correct information logged. If not, it may be the way you send the information from Laravel.
PS: More information may be needed to solve this. When are you triggering the 'countSubcategories' method?
I would do all the intial login in the component itself, and not call a function in template like that. It can drastically affect the performance of the app, since the function would be called on change detection. But first, you are getting [object Promise], since that is exactly what you return, a Promise.
So as already mentioned, I would do the login in the component and then display a property in template. So I suggest the following:
methods: {
countSubcategories(id) {
return axios.get("..." + id);
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
// or use async await pattern
axios.get("...").then(response => {
this.cats = response.data;
// gather all nested requests and perform in parallel
const reqs = this.cats.map(y => this.countSubcategories(y.id));
axios.all(reqs).then(y => {
// merge data
this.cats = this.cats.map((item, i) => {
return {...item, count: y[i].data}
})
});
});
}
}
Now you can display {{cat.count}} in template.
Here's a sample SANDBOX with similar setup.
This is happen 'cause you're trying to render a information who doesn't comeback yet...
Try to change this method inside created, make it async and don't call directly your method on HTML. Them you can render your variable this.childcounts.

Resources