Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document labels: use fields other than _id wherever possible #569

Merged
merged 13 commits into from
Jun 19, 2018

Conversation

PrashanthCorp
Copy link
Contributor

@PrashanthCorp PrashanthCorp commented Apr 28, 2018

Fixes #381
Fixes #145

@@ -33,7 +34,9 @@ export class DocDBDocumentTreeItem implements IAzureTreeItem {
}

public get label(): string {
return this.document.id;
const presentFields = documentDefaultFields.filter(element => this._document.hasOwnProperty(element));
const canonicalField = presentFields.find((element) => this._document[element] && this._document[element].toString() && !this._document[element].toString().startsWith("[object"));
Copy link
Contributor Author

@PrashanthCorp PrashanthCorp Apr 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three conditions in the find :

  1. Removes values being false-y (null, undefined)
  2. Removes cases where the toString is blank ""
  3. Removes cases where this._document[element] is an Object which does not have it's own implementation. In such cases, the toString value returned is one of [object Object], [object null], [object undefined] etc.

A simple type check does not work because typeof <ObjectID> is object ¯\_(ツ)_/¯

src/constants.ts Outdated
@@ -32,3 +32,5 @@ var isAccepted = collection.queryDocuments(

if (!isAccepted) throw new Error('The query was not accepted by the server.');
};`

export const documentDefaultFields = ["name", "Name", "ID", "UUID", "id", "_id"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the DBs case-sensitive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add "Id"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

@@ -33,7 +34,9 @@ export class DocDBDocumentTreeItem implements IAzureTreeItem {
}

public get label(): string {
return this.document.id;
const presentFields = documentDefaultFields.filter(element => this._document.hasOwnProperty(element));
const canonicalField = presentFields.find((element) => this._document[element] && this._document[element].toString() && !this._document[element].toString().startsWith("[object"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typeof == "string"

@@ -33,7 +34,9 @@ export class DocDBDocumentTreeItem implements IAzureTreeItem {
}

public get label(): string {
return this.document.id;
const presentFields = documentDefaultFields.filter(element => this._document.hasOwnProperty(element));
const canonicalField = presentFields.find((element) => this._document[element] && this._document[element].toString() && !this._document[element].toString().startsWith("[object"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multiple calls to toString()

@@ -18,24 +19,22 @@ export class DocDBDocumentTreeItem implements IAzureTreeItem {
public readonly commandId: string = 'cosmosDB.openDocument';

public readonly partitionKeyValue: string | undefined;
public label: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private _label:string;

public get label() {
return this._label;
etc.

ditto for next class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update operation for the collection view in Mongo would have the side-effect of updating a document's label too. In order for that to work, the label field needs a setter. After the offline discussion, it looks like a private+getter+setter is preferred as opposed to public.

src/constants.ts Outdated
@@ -32,3 +32,5 @@ var isAccepted = collection.queryDocuments(

if (!isAccepted) throw new Error('The query was not accepted by the server.');
};`

export const documentDefaultFields = ["name", "Name", "ID", "UUID", "id", "_id"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentLabelFields?

@@ -87,6 +86,7 @@ export class DocDBDocumentTreeItem implements IAzureTreeItem {
}
});
});
this.label = getDocumentTreeItemLabel(this._document);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't you have to call refreshLabel or something?

export function getDocumentTreeItemLabel(document: IMongoDocument | RetrievedDocument): string {
for (let field of documentDefaultFields) {
if (document.hasOwnProperty(field)) {
if (document[field]) { //ignore if false-y value: null, undefined, "".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let value = document[field];
if (value) ...

Slightly better perf, but more importantly, less chance of mistakes or botched bug fixes.

} else { // A simple instanceOf check doesn't seem to work. I run into this issue detailed here :
//https://libertyseeds.ca/2015/01/03/Mongo-ObjectID-fails-instanceof-type-test-in-nodeunit/
const result = document[field].toString();
if (!result.startsWith("[object ")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still not 100% convinced, but I guess we'll see if we hit any issues.

@StephenWeatherford
Copy link
Contributor

What's the status of this?

@@ -24,7 +24,9 @@ export class DocDBDocumentNodeEditor implements ICosmosEditor<RetrievedDocument>
}

public async update(document: RetrievedDocument): Promise<RetrievedDocument> {
return await this._documentNode.treeItem.update(document);
const updatedDoc = await this._documentNode.treeItem.update(document);
this._documentNode.refresh();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to wait for the promise?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say you should unless there's a reason you specifically think we shouldn't.

@@ -52,7 +55,7 @@ export class DocDBDocumentTreeItem implements IAzureTreeItem {
}

public async deleteTreeItem(_node: IAzureNode): Promise<void> {
const message: string = `Are you sure you want to delete document '${this.label}'?`;
const message: string = `Are you sure you want to delete document '${this._label}'?`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Since you have a reader for "label" you should probably still use "label" here.

@@ -38,6 +38,8 @@ export class MongoCollectionNodeEditor implements ICosmosEditor<IMongoDocument[]
const documentNode = documentNodes.find((node) => node.treeItem.document._id.toString() === updatedDoc._id.toString());
if (documentNode) {
documentNode.treeItem.document = updatedDoc;
documentNode.treeItem.label = getDocumentTreeItemLabel(documentNode.treeItem.document);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the right thing to do here is:

  1. put the logic to set _label in refreshLabel()
  2. call documentNode.refresh() whenever you want the label to get refreshed

}

public get id(): string {
return this.document.id;
}

public get label(): string {
return this.document.id;
public async refreshLabel() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return value
no need to be async

Copy link
Contributor Author

@PrashanthCorp PrashanthCorp May 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation.

However, IAzureTreeItem asks for a refreshLabel() that returns a Promise<void> Not having it async or a return type of void instead of Promise<void> gives type errors.

This is also the case in the MongoDocTreeItem class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add the return type on the function header to make it clearer. :)

for (let field of documentLabelFields) {
if (document.hasOwnProperty(field)) {
let value = document[field];
if (value) { //ignore if false-y value: null, undefined, "".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also ignore zero, which I don't think would be desirable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

Looking at the other falsy values, I think we'll be covered after acconting for 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I'd suggest the following:

I'd suggest this:
(note: you're effectively checking that it's not an object with your value.toString().startsWith("[object ")) below. I'm not sure this idea behavior when the property to display is an object or array, but it seems reasonable to skip it and try something else. But typeof is a lot better than the toString.startsWith()

// If doesn't have value, or is null, ObjectId or other object type, try a different property
if (typeof value !== 'undefined' && typeof value !== 'object') {
return String(value);
}

I think that handles it all, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. It relies on null having type object (slightly obfuscated). If that's well known, then I'll go ahead with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, typeof null = "object" in JS. Well-known "bug" that can't be fixed now.

}
}
}
return document["_id"].toString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can _id be undefined or null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. In case of SQL documents, the id field should be present.

I have both these fields in the documentLabelFields, so they should be covered in the loop. What would a good default return be to ensure type-safety?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd leave it as _id, since we'll want to change documentLabelFields to be user-settable anyway. Looks like previous code had return document._id.toString(). Still, I'd rather be conservative and not assume this won't give a null ref exception.

I think that String(document._id) is safe for null/undefined? Can you verify? Thx.

@StephenWeatherford
Copy link
Contributor

Have you reviewed this yourself and looked for issues I might find? There's at least one more issue I haven't mentioned. Thanks.

@@ -25,7 +25,9 @@ export class MongoDocumentNodeEditor implements ICosmosEditor<IMongoDocument> {
}

public async update(document: IMongoDocument): Promise<IMongoDocument> {
return await this._documentNode.treeItem.update(document);
const updatedDoc = await this._documentNode.treeItem.update(document);
this._documentNode.refresh();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to await this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

if (node) {
return (<IAzureNode<MongoDocumentTreeItem>>node).treeItem.update(newDocument);
result = await (<IAzureNode<MongoDocumentTreeItem>>node).treeItem.update(newDocument);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the other issue I saw. Why was the return removed here?

Copy link
Contributor Author

@PrashanthCorp PrashanthCorp May 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. We need to kick off the refresh before returning, but after updating the value.

} else {
// If the node isn't cached already, just update it to Mongo directly (without worrying about updating the tree)
const db = await this._databaseNode.treeItem.getDb();
result = await MongoDocumentTreeItem.update(db.collection(this._collectionName), newDocument);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Felt this control flow looked clearer. If you prefer a return statement after each block (functionally the same), I'll do that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with either one, really depends on the rest of the code as to which is easier to read/maintain (e.g. how many control flows, how big the then case is, etc.)

src/constants.ts Outdated
@@ -34,3 +34,5 @@ export const defaultStoredProcedure =
};` ;

export const emptyPartitionKeyValue = {};

export const documentLabelFields = ["name", "Name", "ID", "UUID", "id", "_id"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add "Id", "uuid"

for (let field of documentLabelFields) {
if (document.hasOwnProperty(field)) {
let value = document[field];
if (value || value === 0) { //ignore if false-y value: null, undefined, "".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were supposed to remove this line:

if (value || value === 0)

if (document.hasOwnProperty(field)) {
let value = document[field];
if (value || value === 0) { //ignore if false-y value: null, undefined, "".
if (value != 'undefined' && typeof value !== 'object') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typeof value

@StephenWeatherford
Copy link
Contributor

@PrashanthCorp CLA will pass if you push a new commit

@PrashanthCorp
Copy link
Contributor Author

Ping.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants