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

remove asyncComputed from MergeTable #8388

Closed
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions openlibrary/components/MergeUI.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
<button class="reject-btn" v-if="showRejectButton" @click="rejectMerge">Reject Merge</button>
</div>
<div id="diffs-toggle">
<label>
<input type="checkbox" title="Show textual differences" v-model="show_diffs" />
Show text diffs
</label>
<label>
RayBB marked this conversation as resolved.
Show resolved Hide resolved
<input type="checkbox" title="Show textual differences" v-model="show_diffs" />
Show text diffs
</label>
</div>
</div>
<pre v-if="mergeOutput">{{mergeOutput}}</pre>
</div>
Expand Down
193 changes: 95 additions & 98 deletions openlibrary/components/MergeUI/MergeTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,9 @@
<script>
/* eslint no-console: 0 */
import _ from 'lodash';
import Vue from 'vue';
import AsyncComputed from 'vue-async-computed';
import MergeRow from './MergeRow.vue';
import { merge, get_editions, get_lists, get_bookshelves, get_ratings } from './utils.js';

Vue.use(AsyncComputed);

/**
* @param {string} olid
*/
Expand All @@ -75,134 +71,104 @@ export default {
},
data() {
return {
master_key: null,
/** @type {{[key: string]: Boolean}} */
selected: []
records: [],
bookshelves: null,
editions: null,
lists: null,
ratings: null
};
},
async created(){
// using await in created won't block the lifecycle, just the rest of the function
await this.getRecords();
this.getBookshelves();
this.getEditions();
this.getLists();
this.getRatings();
},
props: {
olids: Array,
show_diffs: Boolean,
primary: String
},
asyncComputed: {
async records() {
methods: {
isCellUsed(record, field) {
if (!this.merge) return false;
return field in this.merge.sources
? this.merge.sources[field].includes(record.key)
: record.key === this.master_key;
},
async getRecords(){
// gets records from api and sets them
const olids_sorted = _.sortBy(this.olids, olid =>
parseFloat(olid.match(/\d+/)[0])
parseInt(olid.match(/\d+/)[0])
);
// Ensure orphaned editions are at the bottom of the list
const records = _.orderBy(
await Promise.all(olids_sorted.map(fetchRecord)),
record => record.type.key, 'desc');

let masterIndex = 0
if (this.primary) {
const primaryKey = `/works/${this.primary}`
masterIndex = records.findIndex(elem => elem.key === primaryKey)
}

this.master_key = records[masterIndex].key
this.selected = _.fromPairs(records.map(record => [record.key, record.type.key.includes('work')]));

return records;
this.records = records;
},

async editions() {
if (!this.records) return null;

const editionPromises = await Promise.all(
this.records.map(r => r.type.key.includes('work') ? get_editions(r.key) : {size: 0})
);
const editions = editionPromises.map(p => p.value || p);
const editionsMap = _.fromPairs(
this.records.map((work, i) => [work.key, editions[i]])
);
async getBookshelves() {this.bookshelves = await this.fetchData(get_bookshelves)},
async getEditions() {
const editionsMap = await this.fetchData(get_editions, { size: 0 });

// If any of the records are editions, insert the record as its own edition list
Object.keys(editionsMap).forEach((key, index) => {
if (key.includes('M')) editionsMap[key] = {size: 1, entries: [this.records[index]]};
});

return editionsMap;
this.editions = editionsMap;
},

async lists() {
if (!this.records) return null;

// We only need the count, so set limit=0 (waaaay faster!)
const promises = await Promise.all(
this.records.map(r => (r.type.key === '/type/work') ? get_lists(r.key, 0) : {})
);
const responses = promises.map(p => p.value || p);
return _.fromPairs(
this.records.map((work, i) => [work.key, responses[i]])
);
async getLists() {
// TODO: we can simplify if we set default to zero...
this.lists = await this.fetchData((r) => get_lists(r, 0));
},
async bookshelves() {
if (!this.records) return null;
async getRatings() {this.ratings = await this.fetchData(get_ratings)},
async fetchData(getter, defaultValue = {}){
/*
If recordsExist, for each work call the getter and then return a dict with
1. the record key as the key
2. the response as the value
3. the default value if the type isn't a work
*/
if (!this.recordsExist) return null;

const promises = await Promise.all(
this.records.map(r => (r.type.key === '/type/work') ? get_bookshelves(r.key) : {})
);
const responses = promises.map(p => p.value || p);
return _.fromPairs(
this.records.map((work, i) => [work.key, responses[i]])
this.records.map(r => (r.type.key === '/type/work') ? getter(r.key) : defaultValue)
);
},

async ratings() {
if (!this.records) return null;

const promises = await Promise.all(
this.records.map(r => (r.type.key === '/type/work') ? get_ratings(r.key) : {})
);
const responses = promises.map(p => p.value || p);
return _.fromPairs(
this.records.map((work, i) => [work.key, responses[i]])
);
},

async merge() {
if (!this.master_key || !this.records || !this.editions)
return undefined;

const master = this.records.find(r => r.key === this.master_key);
const all_dupes = this.records
.filter(r => this.selected[r.key])
.filter(r => r.key !== this.master_key);
const dupes = all_dupes.filter(r => r.type.key === '/type/work');
const records = [master, ...all_dupes];
const editions_to_move = _.flatMap(
all_dupes,
work => this.editions[work.key].entries
);

const [record, sources] = merge(master, dupes);

const extras = {
edition_count: _.sum(records.map(r => this.editions[r.key].size)),
list_count: (this.lists) ? _.sum(records.map(r => this.lists[r.key].size)) : null
};

const unmergeable_works = this.records
.filter(work => work.type.key === '/type/work' &&
this.selected[work.key] &&
work.key !== this.master_key &&
this.editions[work.key].entries.length < this.editions[work.key].size)
.map(r => r.key);

return { record, sources, ...extras, dupes, editions_to_move, unmergeable_works };
}
},
methods: {
isCellUsed(record, field) {
if (!this.merge) return false;
return field in this.merge.sources
? this.merge.sources[field].includes(record.key)
: record.key === this.master_key;
}
},
computed: {
recordsExist(){
return this.records && this.records.length > 1;
},
selected(){
/** @type {{[key: string]: Boolean}} */
/*
Maybe this and master_key shouldn't be computed since they only matter on first one.
But I think that it's okay since this.records will never change.
*/
if (!this.recordsExist) return []
return _.fromPairs(this.records.map(record => [record.key, record.type.key.includes('work')]));
},
master_key(){
if (!this.recordsExist) return null

let masterIndex = 0;
if (this.primary) {
const primaryKey = `/works/${this.primary}`
masterIndex = this.records.findIndex(elem => elem.key === primaryKey)
}

return this.records[masterIndex].key
},
fields() {
const at_start = ['covers'];
const together = ['key', 'title', 'subtitle', 'authors', 'error'];
Expand Down Expand Up @@ -257,6 +223,37 @@ export default {
...usedTextData,
...otherFields
];
},
merge(){
if (!this.master_key || !this.recordsExist || !this.editions)
return undefined;

const master = this.records.find(r => r.key === this.master_key);
const all_dupes = this.records
.filter(r => this.selected[r.key])
.filter(r => r.key !== this.master_key);
const dupes = all_dupes.filter(r => r.type.key === '/type/work');
const records = [master, ...all_dupes];
const editions_to_move = _.flatMap(
all_dupes,
work => this.editions[work.key].entries
);

const [record, sources] = merge(master, dupes);

const extras = {
edition_count: _.sum(records.map(r => this.editions[r.key].size)),
list_count: (this.lists) ? _.sum(records.map(r => this.lists[r.key].size)) : null
};

const unmergeable_works = this.records
.filter(work => work.type.key === '/type/work' &&
this.selected[work.key] &&
work.key !== this.master_key &&
this.editions[work.key].entries.length < this.editions[work.key].size)
.map(r => r.key);

return { record, sources, ...extras, dupes, editions_to_move, unmergeable_works };
}
}
};
Expand Down
14 changes: 0 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@
"svgo": "2.3.1",
"tesseract.js": "4.1.1",
"vue": "^2.7.0",
"vue-async-computed": "3.9.0",
"vue-loader": "^15.10.0",
"vue-multiselect": "2.1.6",
"webpack": "^5.88.2",
Expand Down