Browse Source

add recipient description to offers in user's list

pull/123/head
Trent Larson 1 month ago
parent
commit
4244e6b279
  1. 9
      CONTRIBUTING.md
  2. 8
      src/libs/endorserServer.ts
  3. 62
      src/views/ProjectsView.vue
  4. 4
      test-playwright/50-record-offer.spec.ts

9
CONTRIBUTING.md

@ -2,5 +2,10 @@
Welcome! We are happy to have your help with this project. Welcome! We are happy to have your help with this project.
Note that all contributions will be under our We expect contributions to include automated tests and pass linting. Run the `test-all` task.
[license, modeled after SQLite](https://github.com/trentlarson/endorser-ch/blob/master/LICENSE). Note that some previous features don't have tests and adding more will make you friends quick.
Note that all contributions will be under our [license, modeled after SQLite](https://github.com/trentlarson/endorser-ch/blob/master/LICENSE).
If you want to see a code of conduct, we're probably not the people you want to hang with.
Basically, we'll work together as long as we both enjoy it, and we'll stop when that stops.

8
src/libs/endorserServer.ts

@ -267,10 +267,6 @@ export type CreateAndSubmitClaimResult = SuccessResult | ErrorResult;
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6 // See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
const HIDDEN_DID = "did:none:HIDDEN"; const HIDDEN_DID = "did:none:HIDDEN";
const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
max: 500,
});
export function isDid(did: string) { export function isDid(did: string) {
return did.startsWith("did:"); return did.startsWith("did:");
} }
@ -507,6 +503,10 @@ export async function getHeaders(did?: string) {
return headers; return headers;
} }
const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
max: 500,
});
/** /**
* @param handleId nullable, in which case "undefined" will be returned * @param handleId nullable, in which case "undefined" will be returned
* @param requesterDid optional, in which case no private info will be returned * @param requesterDid optional, in which case no private info will be returned

62
src/views/ProjectsView.vue

@ -109,6 +109,19 @@
</div> </div>
<div> <div>
<div>
To
{{
offer.fulfillsPlanHandleId
? projectNameFromHandleId[offer.fulfillsPlanHandleId]
: didInfo(
offer.recipientDid,
activeDid,
allMyDids,
allContacts,
)
}}
</div>
<div> <div>
{{ offer.objectDescription }} {{ offer.objectDescription }}
</div> </div>
@ -244,11 +257,14 @@ import QuickNav from "@/components/QuickNav.vue";
import ProjectIcon from "@/components/ProjectIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.vue";
import TopMessage from "@/components/TopMessage.vue"; import TopMessage from "@/components/TopMessage.vue";
import { import {
didInfo,
getHeaders, getHeaders,
getPlanFromCache,
OfferSummaryRecord, OfferSummaryRecord,
PlanData, PlanData,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import EntityIcon from "@/components/EntityIcon.vue"; import EntityIcon from "@/components/EntityIcon.vue";
import { Contact } from "@/db/tables/contacts";
@Component({ @Component({
components: { EntityIcon, InfiniteScroll, QuickNav, ProjectIcon, TopMessage }, components: { EntityIcon, InfiniteScroll, QuickNav, ProjectIcon, TopMessage },
@ -263,16 +279,19 @@ export default class ProjectsView extends Vue {
} }
activeDid = ""; activeDid = "";
allContacts: Array<Contact> = [];
allMyDids: Array<string> = [];
apiServer = ""; apiServer = "";
projects: PlanData[] = []; projects: PlanData[] = [];
isLoading = false; isLoading = false;
isRegistered = false; isRegistered = false;
numAccounts = 0;
offers: OfferSummaryRecord[] = []; offers: OfferSummaryRecord[] = [];
projectNameFromHandleId: Record<string, string> = {}; // mapping from handleId to description
showOffers = true; showOffers = true;
showProjects = false; showProjects = false;
libsUtil = libsUtil; libsUtil = libsUtil;
didInfo = didInfo;
async mounted() { async mounted() {
try { try {
@ -282,9 +301,13 @@ export default class ProjectsView extends Vue {
this.apiServer = (settings?.apiServer as string) || ""; this.apiServer = (settings?.apiServer as string) || "";
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings?.isRegistered;
this.allContacts = await db.contacts.toArray();
await accountsDB.open(); await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count(); const allAccounts = await accountsDB.accounts.toArray();
if (this.numAccounts === 0) { this.allMyDids = allAccounts.map((acc) => acc.did);
if (allAccounts.length === 0) {
console.error("No accounts found."); console.error("No accounts found.");
this.errNote("You need an identifier to load your projects."); this.errNote("You need an identifier to load your projects.");
} else { } else {
@ -343,10 +366,7 @@ export default class ProjectsView extends Vue {
async loadMoreProjectData(payload: boolean) { async loadMoreProjectData(payload: boolean) {
if (this.projects.length > 0 && payload) { if (this.projects.length > 0 && payload) {
const latestProject = this.projects[this.projects.length - 1]; const latestProject = this.projects[this.projects.length - 1];
await this.loadProjects( await this.loadProjects(`beforeId=${latestProject.rowid}`);
this.activeDid,
`beforeId=${latestProject.rowid}`,
);
} }
} }
@ -355,7 +375,7 @@ export default class ProjectsView extends Vue {
* @param issuerDid of the user * @param issuerDid of the user
* @param urlExtra additional url parameters in a string * @param urlExtra additional url parameters in a string
**/ **/
async loadProjects(activeDid?: string, urlExtra: string = "") { async loadProjects(urlExtra: string = "") {
const url = `${this.apiServer}/api/v2/report/plansByIssuer?${urlExtra}`; const url = `${this.apiServer}/api/v2/report/plansByIssuer?${urlExtra}`;
await this.projectDataLoader(url); await this.projectDataLoader(url);
} }
@ -402,7 +422,31 @@ export default class ProjectsView extends Vue {
this.isLoading = true; this.isLoading = true;
const resp = await this.axios.get(url, { headers } as AxiosRequestConfig); const resp = await this.axios.get(url, { headers } as AxiosRequestConfig);
if (resp.status === 200 && resp.data.data) { if (resp.status === 200 && resp.data.data) {
this.offers = this.offers.concat(resp.data.data); // add one-by-one as they retrieve project names, potentially from the server
for (const offer of resp.data.data) {
if (offer.fulfillsPlanHandleId) {
const project = await getPlanFromCache(
offer.fulfillsPlanHandleId,
this.axios,
this.apiServer,
this.activeDid,
);
const projectName = project?.name as string;
console.log(
"now have name for",
offer.fulfillsPlanHandleId,
projectName,
);
this.projectNameFromHandleId[offer.fulfillsPlanHandleId] =
projectName;
console.log(
"now have a real name for",
offer.fulfillsPlanHandleId,
this.projectNameFromHandleId[offer.fulfillsPlanHandleId],
);
}
this.offers = this.offers.concat([offer]);
}
} else { } else {
console.error( console.error(
"Bad server response & data for offers:", "Bad server response & data for offers:",

4
test-playwright/50-record-offer.spec.ts

@ -5,7 +5,7 @@ test('Record an offer', async ({ page }) => {
// Generate a random string of 6 characters, skipping the "0." at the beginning // Generate a random string of 6 characters, skipping the "0." at the beginning
const randomString = Math.random().toString(36).substring(2, 8); const randomString = Math.random().toString(36).substring(2, 8);
// Standard title prefix // Standard title prefix
const finalTitle = `Offer ${randomString}`; const finalTitle = `Offering of ${randomString}`;
const randomNonZeroNumber = Math.floor(Math.random() * 999) + 1; const randomNonZeroNumber = Math.floor(Math.random() * 999) + 1;
// Create new ID for default user // Create new ID for default user
@ -24,7 +24,7 @@ test('Record an offer', async ({ page }) => {
// Refresh home view and check gift // Refresh home view and check gift
await page.goto('./projects'); await page.goto('./projects');
await page.locator('li').filter({ hasText: `All ${randomNonZeroNumber} remaining` }).locator('a').first().click(); await page.locator('li').filter({ hasText: finalTitle }).locator('a').first().click();
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
await expect(page.getByText(finalTitle, { exact: true })).toBeVisible(); await expect(page.getByText(finalTitle, { exact: true })).toBeVisible();
const page1Promise = page.waitForEvent('popup'); const page1Promise = page.waitForEvent('popup');

Loading…
Cancel
Save