Join With AngularFire 2 - javascript

I have seen similar questions asked, but those questions had slightly different data structures to what I'm dealing with. I've looked at:
Joining data between paths based on id using AngularFire
AngularFire2: Perform 'Joins' on FirebaseListObservables using RxJS .map()
This is my data structure:
{
"samples": {
"24084": {
"addInfo": "TEST",
"datePrinted": "8/11/2017 9:42:57 AM",
"equipment": "GR028",
"hmisNumber": "100E",
"lotNumber": "GR0030C659-JM",
"productionNumber": "PN0034781",
"userName": "MCorbett"
},
"24342": {
"addInfo": "test",
"datePrinted": "8/15/2017 11:51:55 AM",
"equipment": "GR025",
"hmisNumber": "100",
"lotNumber": "BR0010P835",
"productionNumber": "PN0035616",
"userName": "MCorbett"
}
},
"scans": {
"-Krlb3tv3oFPtYZp2ErX": {
"inTime": 1502997139131,
"sampleId": "24342"
},
"-KrlbdbCT0us6xE9POm3": {
"inTime": 1502997289573,
"outTime": 1502997292524,
"sampleId": "24342"
},
"-Krlc3vsjiQ9czWYGvA9": {
"inTime": 1502997401784,
"outTime": 1502997404864,
"sampleId": "24084"
}
}
}
As you can see, Samples to Scans have a one to many relationship. What I need to do is populated a table with Sample data joined to scan data. It needs to look like this:
"24342": {
"addInfo": "test",
"datePrinted": "8/15/2017 11:51:55 AM",
"equipment": "GR025",
"hmisNumber": "100",
"lotNumber": "BR0010P835",
"productionNumber": "PN0035616",
"userName": "MCorbett",
"inTime": 1502996197213
}
I need to grab all Scans where outTime is undefined, and then join it to it's corresponding Sample data. Here is what I have tried so far:
// Get samples that have a scan where inTime is populated but outTime is not
getOpenSamples() {
console.log('getopensmaples stareted')
let scanWithSampleList = this.scanSvc.getScansList({
orderBy: 'outTime',
startAt: ''
})
.switchMap(scans => {
let sampleObservables = scans.map(scan => this.getSample(scan.sampleId));
console.log("insisde");
return sampleObservables.length === 0 ?
Observable.of(scans) :
Observable.combineLatest(sampleObservables, (samples) => {
scans.forEach((scan, index) => {
scan.productionNumber = samples[index].productionNumer;
scan.lotNumbner = samples[index].lotNumber;
});
return scans;
});
});
}
This gives me this error:
ERROR in C:/Users/corbetmw/workspace/angular/sample-time-tracking/src/app/samples/shared/sample.service.ts (82,54): Propert
y 'productionNumer' does not exist on type '{}'.
What am I doing wrong here? This seems like a simple enough thing, but I'm having a lot of trouble with it. Do I need to change my data structure? Should I make a component that gets the Scans with undefined outTime and stick it in the table with a parent that can pass the sample ID, or vice versa?

I was able to find a solution which returns an observable of type Scan<> with a single Scan inside of it.
I have this function:
getOpenScans() {
const scansList = this.db.list('/scans', ref => ref.orderByChild('outTime').endAt(null));
return scansList.snapshotChanges().map(arr => {
return arr.map(snap => Object.assign(snap.payload.val(), {
$key: snap.key
}))
})
}
Then, I have this guy:
getOpenSamples() {
let openScans = this.scanSvc.getOpenScans();
let fullSamples = openScans.map(scans => {
for (let scan of scans) {
scan.sample = this.getSample(scan.sampleId);
}
return scans;
});
//fullSamples.subscribe(samples => console.log(samples));
return fullSamples;
}
I am now trying to implement this solution with MatTable in Material2.

Related

How to load data in Ant Design Cascader with two level deep options which load lazily?

I am trying to use the Ant design cascader with expanded sub menu on hover, and because there is a lot of data in the sub menu, I only load them lazily as also potrayed in the ant design cascader website.
My cascader consists of 3 different 'lists' of data. I first load something called flows which themselves have a property item which contains either item_id or items_group_id. Items and items-group are themselves different lists of data. The most important aspect of these we need to know is items and items-group have a label, and a value, which are the text name and the unique ID. Flows are lists with additional data and also always have a list of either a few items or item groups or both. Example of each type coming from the backend:
sampleflow = [
{
"id": 13206,
"rate": 23,
"quantity": 23,
"tax": 5,
"amount": 23,
"flow": 163, // NOTE this id is same as the id of the flow
"items_group": null,
"items": 198,
"item": [
163,
198
]
}
]
sampleItem = [
{
"kit_id": null,
"product_id": {
"id": 198,
"name": "Sample Item"
}
},
{
"kit_id": {
"id": 1,
"name": "Item group random1"
},
"product_id": null
}
]
As you can see, item and itemgroup list only has the data id and name, and I have to figure out myself what type of item it is by seeing which id is being sent; either product_id or kit_id(kit and items-group are the same thing).
Note how backend is sendint he flow id which is the same as the id of the flow itself, this is because when I created this record, the frontend sent the data in the format of { name, value, type }
<Form.List name="items">
{(fields, { add, remove }) => { // code to make multiple of these cascaders
return (
<div>
{fields.map((field, index) => (
<>
<Row align="middle">
<Col span={9}>
<div>
<Form.Item name="item">
<Col>
<Cascader
expandTrigger="hover"
defaultValue={cascaderItemValue[0][index] || []}
// defaultValue={fakeCascaderData}
loadData={loadData}
options={options}
onChange={(e, value) => onChangeCascader(e, index, value)}
placeholder="Select"
changeOnSelect
/>
</Col>
</Form.Item>
</div>
</Col></row></div></Form.List>
....
const loadData = async (selectedOptions) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
const flowItems = await loadAPI(`flows-exp/?id=${selectedOptions[0].value}`);
if (flowItems) {
targetOption.children = flowItems.data.items.map((item) => ({
value: item.kit_id ? item.kit_id.id : item.product_id.id,
label: item.kit_id ? `Item-Group ${item.kit_id.name}` : `Item ${item.product_id.name}`,
type: item.kit_id ? `Item-Group` : `Item `,
}));
}
setOptions([...options]);
};
As you can see I also have it in a function that makes multiple of these according to the user's choice. That added a bit of complexity of indexing every form item else the backend won't accept the data properly nor the frontend will be able to load them in the correct order. Anyways I was able to do this perfectly with a single level dept cascader. I am trying the same concept for this, but the second level depth is making it impossible for me to load the data properly.
const onChangeCascader = (array, index, val) => {
console.log('array, index', array, index, val);
if (array[0] && array[1] && val[1]) {
const name = array[0];
const value = array[1];
const type = val[1]?.type;
setItemsSelectedOptions((prev) => ({ ...prev, [String(index)]: { name, value, type } }));
}
};
Here I am sending the data to backend using this format of { name, value, type } where the name is the flow ID, value is product select id, and because there are two types of products, one item and other item-group, i also need to send which kind it is either item or item-group. ex response: { 192, 1, 'Item'}
Basically it is an amalgamation of the features shown. I was able to load the data from my backend, and show the options perfectly. Then i formatted the data a bit before sending it back to my backend. Here is the fucntion where i format the data coming from the backend so the cascader element can load the data properly:
useEffect(() => {
const fetchData = async (id) => {
const Rawdata = await retrieveAllotmentbyid(allotmentid);
console.log('Rawdata', Rawdata);
if (Rawdata.data.flows) {
responseProductArray = Rawdata.data.flows.map((product) => ({
product_type: product.flow || '',
product_value: product.items || product.items_group,
}));
}
const rawdataflowitems = []
const newselectedOptions = []
for (let i = 0; i < Rawdata.data.flows.length; i++) {
rawdataflowitems[i] = await Promise.all([loadAPI(`flows-exp/?id=${Rawdata.data.flows[i].flow}`)]);
}
console.log('rawdataflowitems', rawdataflowitems);
responseProductArray.map((prod) => {
tempProductArray.push([(prod.product_type), prod.product_value])
})
console.log('responseProductArray', responseProductArray, tempProductArray);
const { data } = Rawdata;
if (data.flows) {
data.items = data.flows;
}
for (let i = 0; i < data.items.length; i++) {
data.items[i].item = tempProductArray[i]
}
const cascaderItemArray = []
if (data.items) {
for (let i = 0; i < data.items.length; i++) {
if (data.items[i].items) {
cascaderItemArray[i] = ['product', data.items[i].items];
onChangeCascader(cascaderItemArray[i], i, 'items')
setCascaderItemValue([...cascaderItemValue, cascaderItemArray]);
} else {
cascaderItemArray[i] = ['kit', data.items[i].items_group];
onChangeCascader(cascaderItemArray[i], i, 'items_group')
setCascaderItemValue([...cascaderItemValue, cascaderItemArray]);
}
}
}
console.log('cleaned data after fetching', data);
console.log('cascaderItemValue', cascaderItemValue);
form.setFieldsValue({
...data,
});
}
if (allotmentid) {
fetchData(allotmentid);
}
}, [allotmentid])
In the fetchdata, there is an api call to the backend which loads the data of what items each of the flow sent from the backend contain. It is impossible for me to load every item in every flow as there are 100s of flows with around 5 items in each. So i only load the data of the flows I am getting from the backend. But the issue is, i am unable to load this data into the options state which i use as the options for the cascader. When a user manually uses the cascader, and hovers over the options, i am able to load the data from the backend and update the options which is a state. What I basically do when a user manually hovers over the cascader items, I take the value of the hovered flow, then make an api call to the backend and load the items data and format them properly. Then I add that to the children of the option which was hoveredd using the onChangeCascader i have mentioned above. I believe this is the issue I am having. After literally spending two shifts of 8 hours, i am very clueless on how to do this, or even if this apporach is correct.
This is how create the initial options for the cascader made up of only flows data:
useEffect(() => {
let flowOpts = [];
if (flows) {
flowOpts = flows.map((flow) => ({
value: flow.id,
label: flow.flow_name,
isLeaf: false,
}));
}
if (options.length == 0) {
setOptions(flowOpts);
}
}, [flows]);
After hovering, the children part is loaded and the items or item-groups are selectable and this is how the options look like:
{
"value": 163,
"label": "Rajah Garner",
"isLeaf": false,
"children": [
{
"value": 198,
"label": "Item 0",
"type": "Item "
},
{
"value": 1,
"label": "Item-Group 78465",
"type": "Item-Group"
}
]
}
Safe to say I have used atleast 100 console logs in this single file and am trying to track the data and its format in every way, but still I am helpless on how to approach this.
I understand this is an obscure and long problem, but i was unable to find anything similar on google or even SO. I even messaged the author of the cascader element from ant design to no avail. Please let me know what else details is needed to atleast discuss this.

Read Content from a JSON file to be able to complement my url search within cypress

I'm currently working with some testing files that will be looking into some records that will provide me some data to be able to do a web search. However, I just want to read a specific input from my file. For the same reason, I added the following:
describe('Search', function () {
beforeEach(() => {
cy.login()
cy.fixture('latestLead.json').then(function (lead) {
this.lead = lead
})
it('Convert Lead to an Opportunity', () => {
cy.readFile('cypress/fixture/latestLead.json').then(r => {
r.forEach((item: any) => {
cy.log(item.Id);
});
});
})
})
My json file is the following:
{
"status": 0,
"result": {
"totalSize": 1,
"done": true,
"records": [
{
"attributes": {
"type": "Lead",
"url": "/services/data/v51.0/sobjects/Test/11111111"
},
"Id": "1111111111111",
"Name": "Andres Test Test"
}
]
}
}
The main issues issue is telling me that 'any' is not right, and my cypress will not run. However, I would like to see if it is a better way to get the 'Id' from my json file. Does anyone have a better idea of how to do this?
I would probably assign the fixture an alias and call that in the tests and have the execution within the cy.get() for the fixture. A few things to note with using fixtures: they are only loaded once, even if data changes.
describe('Search', function () {
cy.fixture('latestLead.json').as('latestLead')
it('Convert Lead to an Opportunity', () => {
cy.get('#latestLead').then((data) => {
var ids = []
data.result.records.forEach((record) => {
ids.push(record.Id)
})
// whatever you need to do with the ids
})
})
})
If you will only ever have the one object in the records array, you could bypass the .forEach() and just reference the variable directly (data.results.records[0].Id).
Also, are you using types anywhere else? I'm not sure why you would set item: any unless you were using types.

Simple $lookup "left join" of the ObjectId of two collections not working in Mongo DB

I am using Mongo DB Atlas with node.js and oboe (oboe streams the results to html). I am new to this all, just learning all these technologies, so explaining in simpler terms will be appreaciated.
The goal is to do a $lookup between two collections, in this case, the collection of 'review' to 'place'. I've researched similar answers, but the either didn't work or were using strings, not ObjectIds.
It's quite simple, just connect the ObjectIds of both of the collections, but I am not able to pull the data out from the "left joined" collection of 'places' when using oboe (see oboe code at bottom, FWIW).
Here is a look at a document from both collections, then the code. What am I doing wrong? I have tried converting them to strings and joining with .toString() and .str, plus put 'place_id.ObjectId' and '_id.ObjectId' for localField and foreignField. Another thing too, is how can I see what is in the cursor to know what I am getting? debug(cursor.ToArray()) didn't work. Thanks in advance.
review
({
"_id": { "$oid": "5fd27fd9647f7bb815c4c946" },
"place_id": { "$oid": "5fbc37c4fc13ae680b00002b" }, // connect this...
"user_id": { "$oid": "5fbc10ecfc13ae232d000068" },
"title": "Black Forest -- unforgettable!",
"description": "The forest was great.",
"score": { "$numberInt": "5" }
}
place
{
"_id": { "$oid": "5fbc37c4fc13ae680b00002b" }, // connected to _id above
"name": "Black Forest (Schwarzwald)",
"category": "activity",
"city": "Freiburg",
"country": "Germany",
"description": "The Black Forest (German: Schwarzwald [ˈʃvaʁtsvalt] (About this soundlisten)) is a large forested mountain range.]",
"image": { "filename": "1607629020164_black_forest.jpg", "mime": "image/jpeg" },
"state": ""
})
router.get('/', async (req, res, next) => {
debug('get all reviews api');
try {
const q = req.query.q;
const collation = { locale: 'en_US', strength: 1 };
const matchStage = {};
if (q) {
matchStage.$text = { $search: q };
}
const pipeline = [
{
$match: matchStage,
},
{
$lookup: {
from: 'place',
localField: 'place_id',
foreignField: '_id',
as: 'place',
},
},
];
const connection = await db.connect();
const cursor = connection.collection('review').aggregate(pipeline, { collation: collation });
// write the JSON file
res.type('application/json');
res.write('[\n');
for await (const doc of cursor) {
res.write(JSON.stringify(doc));
res.write(',\n');
}
res.end('null]');
} catch (err) {
sendError(err, res);
}
});
The cursor goes to oboe and becomes an 'item'. I would expect to use a template string such as {item.place.name} to get the data when putting this into html. That's how I would access it, right?
const performSearch = () => {
seen = 0;
$('stream-data-spinner').removeClass('d-none');
$('#search-results').html('');
const formData = $('#search-place-form').serialize();
oboe('/api/review?' + formData)
.node('![*]', (item) => {
if (item) {
chunk.push(item);
if (chunk.length >= 1000) {
showChunk(chunk);
}
}
return oboe.drop;
})
.done((_) => {
// show the last chunk
showChunk(chunk);
// hide the spinner
outputSpinner.classList.add('d-none');
})
.fail((res) => {
// show the error
outputSeen.textContent = `ERROR: network error`;
outputSeen.classList.add('text-danger');
outputSpinner.classList.add('text-danger');
});
};
From your MongoDB aggregation query, your place field is an array. You may want to $unwind it to flatten it into object for your oboe code to access it.

Adding a new object into a nested array

I want to add a new object for each nested array. I'm calling this function any time I add a product to my orderintake:
add2order(productID, productName, productRatePlans) {
this.orderIntake.push({ productID, productName, productRatePlans });
let i = 0;
this.orderIntake[0].productRatePlans[0].productRatePlanCharges.forEach(element => {
i++;
this.orderIntake[0].productRatePlans[0].productRatePlanCharges[
i
].quantity = this.orderIntake[0].productRatePlans[0].productRatePlanCharges[
i
].defaultQuantity;
});
}
this is an example response from the server:
{
"id": "8adc8f996928b9a4016929c59b943a8f",
"sku": "SKU-00006778",
"Partner_Account_ID__c": null,
"productRatePlans": [
{
"id": "8adce4216928c28d016929c59bff3372",
"status": "Active",
"name": "Enterprise",
"description": null,
"effectiveStartDate": "2016-02-26",
"effectiveEndDate": "2029-02-26",
"productRatePlanCharges": [
{
"id": "8adc8f996928b9a4016929c59d183a92",
"name": "USAGE_COUNTER_2",
"type": "Usage",
"model": "Volume",
"uom": "Each",
"pricingSummary": [
"Up to 5000 Each: USD0 flat fee"
],
"pricing": [
{
...
}
],
"defaultQuantity": null,
"applyDiscountTo": null,
"discountLevel": null,
"discountClass": null,
...
"financeInformation": {
..,
}
}
]
}
],
"productFeatures": [
{
...
}
]
}
The data is being retrived this way from an external REST backend so unfortunately I can't initialize the data including the new property...
so in every productRatePlanCharges there should be 1 new object 'quantity'.
How can I add this field to every productRatePlanCharges?
Right now I'm getting: ERROR
TypeError: Cannot read property 'productRatePlanCharges' of undefined
And how can I make sure I'm always adding this to the last orderIntake element? Don't mind productRatePlans there is only 1 in each orderintake...
thanks for your support!
Here you have to create productDetails object with inititalised array like below so that you won't get the error.
add2order(productID, productName, productRatePlans) {
// Create object like below
let productDetails = { productID : productID, productName : productName, productRatePlans : productRatePlans
}
this.orderIntake.push(productDetails);
let i = 0;
this.orderIntake[0].productRatePlans[0].productRatePlanCharges.forEach(element => {
i++;
this.orderIntake[0].productRatePlans[0].productRatePlanCharges[
i
].quantity = this.orderIntake[0].productRatePlans[0].productRatePlanCharges[
i
].defaultQuantity;
});
}
Hope this will help!
as you used Angular you probably use Typescript too. I recommend that you create a model like your incoming model and there define your quantity: number inside productRatePlanCharges object. then map the incoming data to your own model. therefore you will have a quantity=0 in your model that you can change it later in a loop.
If you want to continue with your own way take a look at this:
Add new attribute (element) to JSON object using JavaScript
there is no problem to add an element to current model almost like you did, and the problem might be somewhere else as your error refers to existence of productRatePlanCharges!
as you used forEach I prefer to use that 'element' and double iterating with i++; is not a good idea to me.
this might be better:
element.quantity = element.defaultQuantity;

Update object with given ID embed in array without restructuring Mongo database

I've got the following document named "clients" which includes id, name and list of projects (array of objects):
{
"_id": {
"$oid": "572225d997bb651819f379f7"
},
"name": "ppg",
"projects": [
{
"name": "aaa",
"job_description": "",
"projectID": 20
},
{
"name": "bbbb",
"job_description": "",
"projectID": 21
}
]
}
I would like to update "job_description" of project with given "projectID" like this:
module.exports.saveJobDesc = function(client, idOfProject, textProvided) {
db.clients.update({ name: client},
{ $set: {'projects.0.job_description': textProvided }});
};
But instead of hardcoded index "0" of array I want to find specific project using "projectID". Is there a way to achieve this without changing the structure of collection and/or document?
If you want to update the "job_description" where name="ppg" and project_id=20 then you can use below mongo query:-
db.clients.update({ "name":"ppg","projects.projectID":20 },{$set: {"projects.$.job_description": "abcd"}})
Please let me know if any thing else is required
You cannot update multiple array elements in single update operation, instead you can update one by one which takes time depends upon number of elements in array and number of such documents in collection. see New operator to update all matching items in an array
db.test2.find().forEach( function(doc) {
var projects = doc.projects;
for(var i=0;i<projects.length;i++){
var project = projects[i];
if(project.projectID == 20){
var field = "projects."+i+".job_description";
var query = {};
query[field] = "textasdsd";
db.test2.update({ _id: doc._id},{ $set:query});
}
}
})

Categories