aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpartouf <partouf@gmail.com>2023-07-06 00:33:51 +0200
committerpartouf <partouf@gmail.com>2023-07-06 00:33:51 +0200
commita9e6aac23a60357f484be7463490a745f16de3ae (patch)
treeed172323249888d03c3947dfd71af4ed5d646107
parent3e3e52a2b39bb0ecf7cad53f5d8c4a6d43058be6 (diff)
downloadcompiler-explorer-gh-8063.tar.gz
compiler-explorer-gh-8063.zip
initial changes to fetch and show creation_date of shortlinkgh-8063
-rw-r--r--lib/handlers/api.ts2
-rw-r--r--lib/handlers/route-api.ts18
-rw-r--r--lib/storage/base.ts8
-rw-r--r--lib/storage/local.ts6
-rw-r--r--lib/storage/null.ts5
-rw-r--r--lib/storage/remote.ts5
-rw-r--r--lib/storage/s3.ts5
-rw-r--r--static/main.ts30
-rw-r--r--static/styles/explorer.scss4
-rw-r--r--test/storage/storage-s3-tests.ts2
-rw-r--r--test/unfurl-tests.ts3
-rw-r--r--views/index.pug2
12 files changed, 76 insertions, 14 deletions
diff --git a/lib/handlers/api.ts b/lib/handlers/api.ts
index d3b280b78..92163fa1c 100644
--- a/lib/handlers/api.ts
+++ b/lib/handlers/api.ts
@@ -154,6 +154,8 @@ export class ApiHandler {
.then(result => {
const config = JSON.parse(result.config);
+ res.header('Link-Created', result.created.toUTCString());
+
if (config.content) {
const normalizer = new ClientStateNormalizer();
normalizer.fromGoldenLayout(config);
diff --git a/lib/handlers/route-api.ts b/lib/handlers/route-api.ts
index d067aac62..0530dab63 100644
--- a/lib/handlers/route-api.ts
+++ b/lib/handlers/route-api.ts
@@ -34,6 +34,7 @@ import * as utils from '../utils.js';
import {ApiHandler} from './api.js';
import {SentryCapture} from '../sentry.js';
+import {ExpandedShortLink} from '../storage/base.js';
export type HandlerConfig = {
compileHandler: any;
@@ -48,6 +49,13 @@ export type HandlerConfig = {
contentPolicyHeader: any;
};
+type ShortLinkMetaData = {
+ ogDescription: string | null;
+ ogAuthor: string | null;
+ ogTitle: string;
+ ogCreated: Date | null;
+};
+
export class RouteAPI {
renderGoldenLayout: any;
storageHandler: StorageBase;
@@ -221,17 +229,19 @@ export class RouteAPI {
return lines.map(line => this.escapeLine(req, line)).join('\n');
}
- getMetaDataFromLink(req: express.Request, link: {config: string; specialMetadata: any} | null, config) {
- const metadata = {
- ogDescription: null as string | null,
- ogAuthor: null as string | null,
+ getMetaDataFromLink(req: express.Request, link: ExpandedShortLink | null, config) {
+ const metadata: ShortLinkMetaData = {
+ ogDescription: null,
+ ogAuthor: null,
ogTitle: 'Compiler Explorer',
+ ogCreated: null,
};
if (link) {
metadata.ogDescription = link.specialMetadata ? link.specialMetadata.description.S : null;
metadata.ogAuthor = link.specialMetadata ? link.specialMetadata.author.S : null;
metadata.ogTitle = link.specialMetadata ? link.specialMetadata.title.S : 'Compiler Explorer';
+ metadata.ogCreated = link.created;
}
if (!metadata.ogDescription) {
diff --git a/lib/storage/base.ts b/lib/storage/base.ts
index 4b0eef05e..84b7f37c1 100644
--- a/lib/storage/base.ts
+++ b/lib/storage/base.ts
@@ -36,6 +36,12 @@ Note that a Hash might end up being longer than this!
const USABLE_HASH_CHECK_LENGTH = 9; // Quite generous
const MAX_TRIES = 4;
+export type ExpandedShortLink = {
+ config: string;
+ specialMetadata: any;
+ created: Date;
+};
+
export abstract class StorageBase {
constructor(protected readonly httpRootDir: string, protected readonly compilerProps: CompilerProps) {}
@@ -128,7 +134,7 @@ export abstract class StorageBase {
abstract findUniqueSubhash(hash: string): Promise<any>;
- abstract expandId(id: string): Promise<{config: string; specialMetadata: any}>;
+ abstract expandId(id: string): Promise<ExpandedShortLink>;
abstract incrementViewCount(id): Promise<any>;
}
diff --git a/lib/storage/local.ts b/lib/storage/local.ts
index 13dd0b7c5..f4968b6af 100644
--- a/lib/storage/local.ts
+++ b/lib/storage/local.ts
@@ -29,7 +29,7 @@ import _ from 'underscore';
import {logger} from '../logger.js';
-import {StorageBase} from './base.js';
+import {ExpandedShortLink, StorageBase} from './base.js';
const MIN_STORED_ID_LENGTH = 6;
@@ -105,14 +105,16 @@ export class StorageLocal extends StorageBase {
throw new Error('Hash too small');
}
- async expandId(id: string) {
+ async expandId(id: string): Promise<ExpandedShortLink> {
const expectedPath = path.join(this.storageFolder, id);
logger.info(`Expanding local id ${id} to ${expectedPath}`);
try {
+ const stats = await fs.stat(expectedPath);
const item = await fs.readJson(expectedPath);
return {
config: item.config,
specialMetadata: null,
+ created: stats.ctime,
};
} catch (err) {
// IO error/Logic error, we have no way to store this right now. Please try again? What to do here?
diff --git a/lib/storage/null.ts b/lib/storage/null.ts
index 90fa9ac6e..7206acf57 100644
--- a/lib/storage/null.ts
+++ b/lib/storage/null.ts
@@ -22,7 +22,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
-import {StorageBase} from './base.js';
+import {ExpandedShortLink, StorageBase} from './base.js';
export class StorageNull extends StorageBase {
static get key() {
@@ -41,10 +41,11 @@ export class StorageNull extends StorageBase {
};
}
- async expandId(id: string) {
+ async expandId(id: string): Promise<ExpandedShortLink> {
return {
config: '{}',
specialMetadata: null,
+ created: new Date(),
};
}
diff --git a/lib/storage/remote.ts b/lib/storage/remote.ts
index f36b8f894..578dab437 100644
--- a/lib/storage/remote.ts
+++ b/lib/storage/remote.ts
@@ -29,7 +29,7 @@ import request from 'request';
import {logger} from '../logger.js';
-import {StorageBase} from './base.js';
+import {ExpandedShortLink, StorageBase} from './base.js';
export class StorageRemote extends StorageBase {
static get key() {
@@ -85,7 +85,7 @@ export class StorageRemote extends StorageBase {
res.send({url: shortlink});
}
- async expandId(id: string) {
+ async expandId(id: string): Promise<ExpandedShortLink> {
const resp = await this.get(`/api/shortlinkinfo/${id}`);
if (resp.statusCode !== 200) throw new Error(`ID ${id} not present in remote storage`);
@@ -93,6 +93,7 @@ export class StorageRemote extends StorageBase {
return {
config: resp.body,
specialMetadata: null,
+ created: new Date(),
};
}
diff --git a/lib/storage/s3.ts b/lib/storage/s3.ts
index 3b70f492d..ae17ee597 100644
--- a/lib/storage/s3.ts
+++ b/lib/storage/s3.ts
@@ -32,7 +32,7 @@ import {logger} from '../logger.js';
import {S3Bucket} from '../s3-handler.js';
import {anonymizeIp} from '../utils.js';
-import {StorageBase} from './base.js';
+import {ExpandedShortLink, StorageBase} from './base.js';
/*
* NEVER CHANGE THIS VALUE
@@ -157,7 +157,7 @@ export class StorageS3 extends StorageBase {
};
}
- async expandId(id: string) {
+ async expandId(id: string): Promise<ExpandedShortLink> {
// By just getting the item and not trying to update it, we save an update when the link does not exist
// for which we have less resources allocated, but get one extra read (But we do have more reserved for it)
const item = await this.dynamoDb.getItem({
@@ -173,6 +173,7 @@ export class StorageS3 extends StorageBase {
return {
config: unwrap(result.data).toString(),
specialMetadata: metadata,
+ created: new Date(attributes.creation_date?.S || ''),
};
}
diff --git a/static/main.ts b/static/main.ts
index bb30773f3..d091e1202 100644
--- a/static/main.ts
+++ b/static/main.ts
@@ -391,8 +391,10 @@ function initializeResetLayoutLink() {
const currentUrl = document.URL;
if (currentUrl.includes('/z/')) {
$('#ui-brokenlink').attr('href', currentUrl.replace('/z/', '/resetlayout/')).show();
+ initShortlinkInfoButton();
} else {
$('#ui-brokenlink').hide();
+ hideShortlinkInfoButton();
}
}
@@ -532,6 +534,34 @@ function getDefaultLangId(subLangId: LanguageKey | undefined, options: CompilerE
return defaultLangId;
}
+function hideShortlinkInfoButton() {
+ const div = $('.shortlinkInfo');
+ div.hide();
+}
+
+function showShortlinkInfoButton() {
+ const div = $('.shortlinkInfo');
+ div.show();
+}
+
+function initShortlinkInfoButton() {
+ const button = $('.shortlinkInfo div');
+ if (options.metadata && options.metadata['ogCreated']) {
+ button.popover({
+ html: true,
+ title: 'Link created at',
+ content: options.metadata['ogCreated'] || '',
+ template:
+ '<div class="popover" role="tooltip">' +
+ '<div class="arrow"></div>' +
+ '<h3 class="popover-header"></h3><div class="popover-body"></div>' +
+ '</div>',
+ });
+
+ showShortlinkInfoButton();
+ }
+}
+
// eslint-disable-next-line max-statements
function start() {
initializeResetLayoutLink();
diff --git a/static/styles/explorer.scss b/static/styles/explorer.scss
index 80b2fb245..d654ff30f 100644
--- a/static/styles/explorer.scss
+++ b/static/styles/explorer.scss
@@ -1427,3 +1427,7 @@ html[data-theme='pink'] {
margin-top: 5px;
}
}
+
+.shortlinkInfo div {
+ height: 100%;
+}
diff --git a/test/storage/storage-s3-tests.ts b/test/storage/storage-s3-tests.ts
index cc694fcbf..c00691c6f 100644
--- a/test/storage/storage-s3-tests.ts
+++ b/test/storage/storage-s3-tests.ts
@@ -190,7 +190,7 @@ describe('Retrieves from s3', () => {
.resolves({Body: sdkStreamMixin(stream)});
const result = await storage.expandId('ABCDEF');
- result.should.deep.equal({config: 'I am a monkey', specialMetadata: null});
+ result.should.deep.equal({config: 'I am a monkey', specialMetadata: null, created: new Date('')});
});
it('should handle failures', async () => {
const storage = new StorageS3(httpRootDir, compilerProps, awsProps);
diff --git a/test/unfurl-tests.ts b/test/unfurl-tests.ts
index 8d611605c..1f96ef15e 100644
--- a/test/unfurl-tests.ts
+++ b/test/unfurl-tests.ts
@@ -79,6 +79,7 @@ describe('Basic unfurls', () => {
ogAuthor: null,
ogDescription: '',
ogTitle: 'Compiler Explorer',
+ ogCreated: undefined,
});
});
@@ -109,6 +110,7 @@ describe('Basic unfurls', () => {
ogDescription:
'\ntemplate&lt;typename T&gt;\nconcept TheSameAndAddable = requires(T a, T b) {\n {a+b} -&gt; T;\n};\n\ntemplate&lt;TheSameAndAddable T&gt;\nT sum(T x, T y) {\n return x + y;\n}\n\n#include &lt;string&gt;\n\nint main() {\n int z = 0;\n int w;\n\n return sum(z, w);\n}\n',
ogTitle: 'Compiler Explorer - C++',
+ ogCreated: undefined,
});
});
@@ -138,6 +140,7 @@ describe('Basic unfurls', () => {
ogAuthor: null,
ogDescription: 'project(hello)\n\nadd_executable(output.s\n example.cpp\n square.cpp)\n',
ogTitle: 'Compiler Explorer - C++',
+ ogCreated: undefined,
});
});
});
diff --git a/views/index.pug b/views/index.pug
index ec86473e0..e0e86913d 100644
--- a/views/index.pug
+++ b/views/index.pug
@@ -44,6 +44,8 @@ block prepend content
| Apply Default Font Scale
li.nav-item
button.btn.btn-light.nav-link#loadSiteTemplate(role="button") Templates
+ li.nav-item.shortlinkInfo
+ .nav-link.btn-light.fa.fa-info-circle(role="button")
li.nav-item.ui-presentation-control.d-none
a.nav-link.ui-presentation-first(href="javascript:;")
span.dropdown-icon.fas.fa-fast-backward