Harden gr-formatted-text agains slow/failed project configs

Formerly, if a formatted text component tried to render without the
project config (used by inner linked text components) it would
temporarily fall-back to rendering the unformatted (and un-linkified)
text via `.textContent` -- mirroring the behavior of gr-linked-text.

The result is formatted text elements (when rendered without a project
config) appear as one long line of text. Unlike linkification, however,
text can be accurately formatted with or without the project config --
so this disruptive, poor UX is unnecessary.

The formatted text component is updated to format text when the project
config has not provided, and to re-render when the config has been
provided.

In order to propagate project config loads to the formatted text
components hosted by diff comments, the REST calls must be made by the
diff thread component. To make this call, the thread must have the
project's name, so gr-diff-builder is updated to provide this name to
thread components.

Bug: Issue 6686
Change-Id: I8d09c740930500e99cb5f87b92f4d72f3f50a9ce
This commit is contained in:
Wyatt Allen
2017-07-31 14:33:16 -07:00
parent c3e4ec1b37
commit f0eb4bbf53
17 changed files with 71 additions and 34 deletions

View File

@@ -338,6 +338,7 @@ limitations under the License.
patch-range="[[patchRange]]"
path="[[file.__path]]"
prefs="[[diffPrefs]]"
project-name="[[change.project]]"
project-config="[[projectConfig]]"
on-line-selected="_onLineSelected"
no-render-on-prefs-change

View File

@@ -19,9 +19,10 @@
const IMAGE_MIME_PATTERN = /^image\/(bmp|gif|jpeg|jpg|png|tiff|webp)$/;
function GrDiffBuilderImage(diff, comments, prefs, outputEl, baseImage,
revisionImage) {
GrDiffBuilderSideBySide.call(this, diff, comments, prefs, outputEl, []);
function GrDiffBuilderImage(
diff, comments, prefs, projectName, outputEl, baseImage, revisionImage) {
GrDiffBuilderSideBySide.call(
this, diff, comments, prefs, projectName, outputEl, []);
this._baseImage = baseImage;
this._revisionImage = revisionImage;
}

View File

@@ -17,8 +17,10 @@
// Prevent redefinition.
if (window.GrDiffBuilderSideBySide) { return; }
function GrDiffBuilderSideBySide(diff, comments, prefs, outputEl, layers) {
GrDiffBuilder.call(this, diff, comments, prefs, outputEl, layers);
function GrDiffBuilderSideBySide(
diff, comments, prefs, projectName, outputEl, layers) {
GrDiffBuilder.call(
this, diff, comments, prefs, projectName, outputEl, layers);
}
GrDiffBuilderSideBySide.prototype = Object.create(GrDiffBuilder.prototype);
GrDiffBuilderSideBySide.prototype.constructor = GrDiffBuilderSideBySide;

View File

@@ -17,8 +17,10 @@
// Prevent redefinition.
if (window.GrDiffBuilderUnified) { return; }
function GrDiffBuilderUnified(diff, comments, prefs, outputEl, layers) {
GrDiffBuilder.call(this, diff, comments, prefs, outputEl, layers);
function GrDiffBuilderUnified(
diff, comments, prefs, projectName, outputEl, layers) {
GrDiffBuilder.call(
this, diff, comments, prefs, projectName, outputEl, layers);
}
GrDiffBuilderUnified.prototype = Object.create(GrDiffBuilder.prototype);
GrDiffBuilderUnified.prototype.constructor = GrDiffBuilderUnified;

View File

@@ -94,6 +94,7 @@ limitations under the License.
isImageDiff: Boolean,
baseImage: Object,
revisionImage: Object,
projectName: String,
_builder: Object,
_groups: Array,
_layers: Array,
@@ -219,9 +220,9 @@ limitations under the License.
},
createCommentThreadGroup(changeNum, patchNum, path,
isOnParent, commentSide, projectConfig) {
isOnParent, commentSide) {
return this._builder.createCommentThreadGroup(changeNum, patchNum,
path, isOnParent, commentSide, projectConfig);
path, isOnParent, commentSide);
},
emitGroup(group, sectionEl) {
@@ -252,13 +253,14 @@ limitations under the License.
_getDiffBuilder(diff, comments, prefs) {
if (this.isImageDiff) {
return new GrDiffBuilderImage(diff, comments, prefs,
this.diffElement, this.baseImage, this.revisionImage);
this.projectName, this.diffElement, this.baseImage,
this.revisionImage);
} else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
return new GrDiffBuilderSideBySide(
diff, comments, prefs, this.diffElement, this._layers);
return new GrDiffBuilderSideBySide(diff, comments, prefs,
this.projectName, this.diffElement, this._layers);
} else if (this.viewMode === DiffViewMode.UNIFIED) {
return new GrDiffBuilderUnified(
diff, comments, prefs, this.diffElement, this._layers);
return new GrDiffBuilderUnified(diff, comments, prefs,
this.projectName, this.diffElement, this._layers);
}
throw Error('Unsupported diff view mode: ' + this.viewMode);
},

View File

@@ -30,10 +30,11 @@
const REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
function GrDiffBuilder(diff, comments, prefs, outputEl, layers) {
function GrDiffBuilder(diff, comments, prefs, projectName, outputEl, layers) {
this._diff = diff;
this._comments = comments;
this._prefs = prefs;
this._projectName = projectName;
this._outputEl = outputEl;
this.groups = [];
@@ -336,14 +337,14 @@
};
GrDiffBuilder.prototype.createCommentThreadGroup = function(changeNum,
patchNum, path, isOnParent, projectConfig, range) {
patchNum, path, isOnParent, range) {
const threadGroupEl =
document.createElement('gr-diff-comment-thread-group');
threadGroupEl.changeNum = changeNum;
threadGroupEl.patchForNewThreads = patchNum;
threadGroupEl.path = path;
threadGroupEl.isOnParent = isOnParent;
threadGroupEl.projectConfig = projectConfig;
threadGroupEl.projectName = this._projectName;
threadGroupEl.range = range;
return threadGroupEl;
};
@@ -370,8 +371,7 @@
this._comments.meta.changeNum,
patchNum,
this._comments.meta.path,
isOnParent,
this._comments.meta.projectConfig);
isOnParent);
threadGroupEl.comments = comments;
if (opt_side) {
threadGroupEl.setAttribute('data-side', opt_side);

View File

@@ -62,13 +62,16 @@ limitations under the License.
setup(() => {
stub('gr-rest-api-interface', {
getLoggedIn() { return Promise.resolve(false); },
getProjectConfig() { return Promise.resolve({}); },
});
const prefs = {
line_length: 10,
show_tabs: true,
tab_size: 4,
};
builder = new GrDiffBuilder({content: []}, {left: [], right: []}, prefs);
const projectName = 'my-project';
builder = new GrDiffBuilder(
{content: []}, {left: [], right: []}, prefs, projectName);
});
test('context control buttons', () => {
@@ -265,7 +268,7 @@ limitations under the License.
assert.equal(threadGroupEl.patchForNewThreads, patchNum);
assert.equal(threadGroupEl.path, '/path/to/foo');
assert.equal(threadGroupEl.isOnParent, isOnParent);
assert.deepEqual(threadGroupEl.projectConfig, {foo: 'bar'});
assert.deepEqual(threadGroupEl.projectName, 'my-project');
assert.deepEqual(threadGroupEl.comments, comments);
}
@@ -761,7 +764,7 @@ limitations under the License.
outputEl = element.queryEffectiveChildren('#diffTable');
sandbox.stub(element, '_getDiffBuilder', () => {
const builder = new GrDiffBuilder(
{content}, {left: [], right: []}, prefs, outputEl);
{content}, {left: [], right: []}, prefs, 'my-project', outputEl);
sandbox.stub(builder, 'addColumns');
builder.buildSectionElement = function(group) {
const section = document.createElement('stub');

View File

@@ -38,7 +38,7 @@ limitations under the License.
location-range="[[thread.locationRange]]"
patch-num="[[thread.patchNum]]"
path="[[path]]"
project-config="[[projectConfig]]"></gr-diff-comment-thread>
project-name="[[projectName]]"></gr-diff-comment-thread>
</template>
</template>
<script src="gr-diff-comment-thread-group.js"></script>

View File

@@ -23,8 +23,8 @@
type: Array,
value() { return []; },
},
projectName: String,
patchForNewThreads: String,
projectConfig: Object,
range: Object,
isOnParent: {
type: Boolean,

View File

@@ -61,7 +61,7 @@ limitations under the License.
show-actions="[[_showActions]]"
comment-side="[[comment.__commentSide]]"
side="[[comment.side]]"
project-config="[[projectConfig]]"
project-config="[[_projectConfig]]"
on-create-fix-comment="_handleCommentFix"
on-comment-discard="_handleCommentDiscard"></gr-diff-comment>
</template>

View File

@@ -40,7 +40,10 @@
commentSide: String,
patchNum: String,
path: String,
projectConfig: Object,
projectName: {
type: String,
observer: '_projectNameChanged',
},
isOnParent: {
type: Boolean,
value: false,
@@ -53,6 +56,7 @@
type: Boolean,
notify: true,
},
_projectConfig: Object,
},
behaviors: [
@@ -352,5 +356,16 @@
_computeHostClass(unresolved) {
return unresolved ? 'unresolved' : '';
},
/**
* Load the project config when a project name has been provided.
* @param {string} name The project name.
*/
_projectNameChanged(name) {
if (!name) { return; }
this.$.restAPI.getProjectConfig(name).then(config => {
this._projectConfig = config;
});
},
});
})();

View File

@@ -161,6 +161,17 @@ limitations under the License.
lastComment.__draft = true;
assert.equal(element._hideActions(showActions, lastComment), true);
});
test('setting project name loads the project config', done => {
const projectName = 'foo/bar/baz';
const getProjectStub = sandbox.stub(element.$.restAPI, 'getProjectConfig')
.returns(Promise.resolve({}));
element.projectName = projectName;
flush(() => {
assert.isTrue(getProjectStub.calledWithExactly(projectName));
done();
});
});
});
suite('comment action tests', () => {

View File

@@ -328,6 +328,7 @@ limitations under the License.
path="[[_path]]"
prefs="[[_prefs]]"
project-config="[[_projectConfig]]"
project-name="[[_change.project]]"
view-mode="[[_diffMode]]"
on-line-selected="_onLineSelected">
</gr-diff>

View File

@@ -229,6 +229,7 @@ limitations under the License.
<gr-diff-builder
id="diffBuilder"
comments="[[comments]]"
project-name="[[projectName]]"
diff="[[_diff]]"
diff-path="[[path]]"
view-mode="[[viewMode]]"

View File

@@ -58,6 +58,7 @@
type: Object,
observer: '_projectConfigChanged',
},
projectName: String,
displayLine: {
type: Boolean,
value: false,
@@ -305,8 +306,7 @@
let threadGroupEl = this._getThreadGroupForLine(contentEl);
if (!threadGroupEl) {
threadGroupEl = this.$.diffBuilder.createCommentThreadGroup(
this.changeNum, patchNum, this.path, isOnParent,
this.projectConfig);
this.changeNum, patchNum, this.path, isOnParent);
contentEl.appendChild(threadGroupEl);
}

View File

@@ -60,7 +60,7 @@
// request for it still being in flight), set the content anyway to
// prevent waiting on the config to display the text.
if (this.config) { return; }
this.$.container.textContent = content;
this._contentOrConfigChanged(content);
},
/**

View File

@@ -363,12 +363,10 @@ limitations under the License.
assert.equal(result, expected);
});
test('_contentOrConfigChanged not called without config', () => {
const contentStub = sandbox.stub(element, '_contentChanged');
const contentConfigStub = sandbox.stub(element, '_contentOrConfigChanged');
test('_computeNodes called without config', () => {
const computeNodesSpy = sandbox.spy(element, '_computeNodes');
element.content = 'some text';
assert.isTrue(contentStub.called);
assert.isFalse(contentConfigStub.called);
assert.isTrue(computeNodesSpy.called);
});
test('_contentOrConfigChanged called with config', () => {