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

Show Central version in modal #1099

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
43 changes: 5 additions & 38 deletions src/components/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,11 @@ import { START_LOCATION, useRouter, useRoute } from 'vue-router';
import Alert from './alert.vue';
import Navbar from './navbar.vue';

import useCallWait from '../composables/call-wait';
import useCentralVersion from '../composables/central-version';
import useDisabled from '../composables/disabled';
import useFeatureFlags from '../composables/feature-flags';
import { useRequestData } from '../request-data';
import { useSessions } from '../util/session';
import { loadAsync } from '../util/load-async';
import { useSessions } from '../util/session';

export default {
name: 'App',
Expand All @@ -61,7 +60,9 @@ export default {
inject: ['alert', 'config'],
setup() {
const { visiblyLoggedIn } = useSessions();
useCentralVersion();
useDisabled();
const { features } = useFeatureFlags();

const router = useRouter();
const route = useRoute();
Expand All @@ -71,11 +72,7 @@ export default {
document.documentElement.style.backgroundColor = 'var(--color-accent-secondary)';
});

const { features } = useFeatureFlags();

const { centralVersion } = useRequestData();
const { callWait } = useCallWait();
return { visiblyLoggedIn, centralVersion, callWait, features };
return { features, visiblyLoggedIn };
},
computed: {
routerReady() {
Expand All @@ -86,41 +83,11 @@ export default {
this.visiblyLoggedIn;
},
},
created() {
this.callWait('checkVersion', this.checkVersion, (tries) =>
(tries === 0 ? 15000 : 60000));
},
// Reset backgroundColor after each test.
beforeUnmount() {
document.documentElement.style.backgroundColor = '';
},
methods: {
checkVersion() {
const previousVersion = this.centralVersion.versionText;
return this.centralVersion.request({
url: '/version.txt',
clear: false,
alert: false
})
.then(() => {
if (previousVersion == null || this.centralVersion.versionText === previousVersion)
return false;

// Alert the user about the version change, then keep alerting them.
// One benefit of this approach is that the user should see the alert
// even if there is another alert (say, about session expiration).
this.callWait(
'alertVersionChange',
() => { this.alert.info(this.$t('alert.versionChange')); },
(count) => (count === 0 ? 0 : 60000)
);
return true;
})
// This error could be the result of logout, which will cancel all
// requests.
.catch(error =>
(error.response != null && error.response.status === 404));
},
hideAlertAfterClick(event) {
if (this.alert.state && event.target.closest('a[target="_blank"]') != null &&
!event.defaultPrevented) {
Expand Down
64 changes: 64 additions & 0 deletions src/components/central-version.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!--
Copyright 2024 ODK Central Developers
See the NOTICE file at the top-level directory of this distribution and at
https://github.com/getodk/central-frontend/blob/master/NOTICE.

This file is part of ODK Central. It is subject to the license terms in
the LICENSE file found in the top-level directory of this distribution and at
https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central,
including this file, may be copied, modified, propagated, or distributed
except according to the terms contained in the LICENSE file.
-->
<template>
<modal id="central-version" :state="state" hideable size="large" backdrop
@hide="$emit('hide')">
<template #title>{{ $t('title') }}</template>
<template #body>
<div class="modal-introduction">
<p>{{ $t('shortVersion', { version: centralVersion.currentVersion }) }}</p>
<p>{{ $t('longVersion') }}</p>
<pre><code><selectable wrap>{{ centralVersion.versionText }}</selectable></code></pre>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-primary" @click="$emit('hide')">
{{ $t('action.close') }}
</button>
</div>
</template>
</modal>
</template>

<script setup>
import Modal from './modal.vue';
import Selectable from './selectable.vue';

import useCentralVersion from '../composables/central-version';

defineOptions({
name: 'CentralVersion'
});
defineProps({
state: Boolean
});
defineEmits(['hide']);

const centralVersion = useCentralVersion();
</script>

<style lang="scss">
#central-version {
.loading { min-height: 120px; }
}
</style>

<i18n lang="json5">
{
"en": {
// This is the title at the top of a pop-up. It refers to the version of ODK
// Central that the user is using.
"title": "Central Version",
"shortVersion": "You are using ODK Central {version}.",
"longVersion": "You can find more detailed version information below:"
}
}
</i18n>
12 changes: 10 additions & 2 deletions src/components/navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@ except according to the terms contained in the LICENSE file.
{{ $t('analyticsNotice') }}
</a>
<ul class="nav navbar-nav">
<navbar-help-dropdown/>
<navbar-help-dropdown @show-version="versionModal.show()"/>
<navbar-locale-dropdown/>
<navbar-actions/>
</ul>
</div>
</div>
</div>
</nav>

<central-version-component v-if="centralVersion != null"
v-bind="versionModal" @hide="versionModal.hide()"/>
<analytics-introduction v-if="config.loaded && config.showsAnalytics"
v-bind="analyticsIntroduction" @hide="analyticsIntroduction.hide()"/>
</div>
Expand All @@ -48,11 +51,13 @@ except according to the terms contained in the LICENSE file.
<script>
import { defineAsyncComponent } from 'vue';

import CentralVersion from './central-version.vue';
import NavbarActions from './navbar/actions.vue';
import NavbarHelpDropdown from './navbar/help-dropdown.vue';
import NavbarLinks from './navbar/links.vue';
import NavbarLocaleDropdown from './navbar/locale-dropdown.vue';

import useCentralVersion from '../composables/central-version';
import useRoutes from '../composables/routes';
import { loadAsync } from '../util/load-async';
import { modalData } from '../util/reactivity';
Expand All @@ -62,6 +67,7 @@ export default {
name: 'Navbar',
components: {
AnalyticsIntroduction: defineAsyncComponent(loadAsync('AnalyticsIntroduction')),
CentralVersionComponent: CentralVersion,
NavbarActions,
NavbarHelpDropdown,
NavbarLinks,
Expand All @@ -72,11 +78,13 @@ export default {
// The component does not assume that this data will exist when the
// component is created.
const { currentUser, analyticsConfig } = useRequestData();
const centralVersion = useCentralVersion();
const { canRoute } = useRoutes();
return { currentUser, analyticsConfig, canRoute };
return { currentUser, analyticsConfig, centralVersion, canRoute };
},
data() {
return {
versionModal: modalData(),
analyticsIntroduction: modalData('AnalyticsIntroduction')
};
},
Expand Down
15 changes: 14 additions & 1 deletion src/components/navbar/help-dropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ except according to the terms contained in the LICENSE file.
<a href="https://forum.getodk.org/" target="_blank">{{ $t('common.forum') }}</a>
</li>
<li>
<a href="/version.txt" target="_blank">{{ $t('common.version') }}</a>
<a href="/version.txt" target="_blank" @click="showVersion">
sadiqkhoja marked this conversation as resolved.
Show resolved Hide resolved
{{ $t('common.version') }}
</a>
</li>
</ul>
</li>
Expand All @@ -32,9 +34,20 @@ except according to the terms contained in the LICENSE file.
<script setup>
import DocLink from '../doc-link.vue';

import useCentralVersion from '../../composables/central-version';

defineOptions({
name: 'NavbarHelpDropdown'
});
const emit = defineEmits(['show-version']);

const centralVersion = useCentralVersion();
const showVersion = (event) => {
if (centralVersion.value != null) {
event.preventDefault();
emit('show-version');
}
};
</script>

<i18n lang="json5">
Expand Down
21 changes: 12 additions & 9 deletions src/components/outdated-version.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,45 @@ except according to the terms contained in the LICENSE file.

<script setup>
import { computed, inject } from 'vue';
import { useRequestData } from '../request-data';

const { centralVersion, currentUser } = useRequestData();
import useCentralVersion from '../composables/central-version';
import { useRequestData } from '../request-data';

defineOptions({
name: 'OutdatedVersion'
});

const { currentUser } = useRequestData();

const visiblyLoggedIn = inject('visiblyLoggedIn');

const container = inject('container');
const { i18n: globalI18n } = container;
const { i18n: globalI18n, currentDate } = container;
const locale = computed(() => globalI18n.locale);

const iframeSrc = computed(() => `https://getodk.github.io/central/outdated-version.html?version=${centralVersion.currentVersion}&lang=${locale.value}`);
const centralVersion = useCentralVersion();
const iframeSrc = computed(() => `https://getodk.github.io/central/outdated-version.html?version=${centralVersion.value.currentVersion}&lang=${locale.value}`);

const showBanner = computed(() => {
// user is not logged in or doesn't have ability to set config (implying not an admin)
if (!currentUser.dataExists || !visiblyLoggedIn || !currentUser.can('config.set')) return false;
if (!centralVersion.dataExists) return false;
if (centralVersion.value == null) return false;

// User has seen the warning in the last 30 days, so don't show it again
// 864E5 is the number of milliseconds in a day
const dismissDate = currentUser.preferences?.site?.outdatedVersionWarningDismissDate;
if (dismissDate && centralVersion.currentDate.getTime() < (new Date(dismissDate).getTime() + (864E5 * 30))) return false;
if (dismissDate && currentDate.value.getTime() < (new Date(dismissDate).getTime() + (864E5 * 30))) return false;

// Difference between current year and Central version year is less than 2
const centralVersionYear = Number(centralVersion.currentVersion.match(/^(\d{4})/)[1]);
const currentYear = centralVersion.currentDate.getFullYear();
const centralVersionYear = Number(centralVersion.value.currentVersion.match(/^(\d{4})/)[1]);
const currentYear = currentDate.value.getFullYear();
if (currentYear - centralVersionYear < 2) return false;

return true;
});

const dismiss = () => {
currentUser.preferences.site.outdatedVersionWarningDismissDate = centralVersion.currentDate;
currentUser.preferences.site.outdatedVersionWarningDismissDate = currentDate.value;
};
</script>

Expand Down
13 changes: 10 additions & 3 deletions src/components/selectable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ including this file, may be copied, modified, propagated, or distributed
except according to the terms contained in the LICENSE file.
-->
<template>
<div ref="el" class="selectable" @click="select"><!-- eslint-disable-line vuejs-accessibility/click-events-have-key-events, vue/multiline-html-element-content-newline -->
<div ref="el" class="selectable" :class="{ scroll: !wrap }" @click="select"><!-- eslint-disable-line vuejs-accessibility/click-events-have-key-events, vue/multiline-html-element-content-newline -->
<slot></slot>
</div>
</template>

<script setup>
import { ref } from 'vue';

defineProps({
wrap: Boolean
});

const el = ref(null);
const select = () => {
const selection = getSelection();
Expand All @@ -31,7 +35,10 @@ const select = () => {

.selectable {
font-family: $font-family-monospace;
overflow-x: auto;
white-space: nowrap;

&.scroll {
overflow-x: auto;
white-space: nowrap;
}
}
</style>
61 changes: 61 additions & 0 deletions src/composables/central-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Copyright 2024 ODK Central Developers
See the NOTICE file at the top-level directory of this distribution and at
https://github.com/getodk/central-frontend/blob/master/NOTICE.

This file is part of ODK Central. It is subject to the license terms in
the LICENSE file found in the top-level directory of this distribution and at
https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central,
including this file, may be copied, modified, propagated, or distributed
except according to the terms contained in the LICENSE file.
*/
import { F } from 'ramda';
import { computed, watchEffect } from 'vue';

import useCallWait from './call-wait';
import { memoizeForContainer } from '../util/composable';
import { useRequestData } from '../request-data';

export default memoizeForContainer(({ i18n, alert, config }) => {
const centralVersion = computed(() => {
if (!config.dataExists) return null;
const versionText = config.centralVersion;
if (versionText == null) return null;
return {
versionText,
currentVersion: versionText.match(/\(v(\d{4}[^-]*)/)[1]
};
});

// Check for a change to /version.txt.
const { createResource } = useRequestData();
const latestVersion = createResource('latestVersion');
const { callWait } = useCallWait();
// Alerts the user about a version change, then keep alerting them. One
// benefit of this approach is that the user should see the alert even if
// there is another alert (say, about session expiration).
const alertAboutChange = () => {
callWait(
'centralVersion.alert',
() => { alert.info(i18n.t('alert.versionChange')); },
(count) => (count === 0 ? 0 : 60000)
);
};
watchEffect(() => {
if (centralVersion.value == null) return;
callWait(
'centralVersion.check',
() => latestVersion.request({ url: '/version.txt', alert: false })
.then(() => {
if (latestVersion.data === centralVersion.value.versionText)
return false;
alertAboutChange();
return true;
})
.catch(F),
() => 60000
);
});

return centralVersion;
});
3 changes: 3 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ except according to the terms contained in the LICENSE file.
// These are the default config values. They will be merged with the response
// for /client-config.json.
export default {
centralVersion: process.env.NODE_ENV === 'development'
? `(v${new Date().getFullYear()}.1.0-sha)\nNote: fake version for development.`
: null,
// `true` to allow navigation to /system/analytics and `false` not to.
showsAnalytics: true,
home: {
Expand Down
Loading