8000 changes to codeninja club/camp creation - show option of esports by mrfinch · Pull Request #7911 · codecombat/codecombat · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

changes to codeninja club/camp creation - show option of esports #7911

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

Merged
merged 5 commits into from
Jan 29, 2025
Merged
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
1 change: 1 addition & 0 deletions app/components/common/Navigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export default Vue.extend({

homeLink () {
if (me.isCodeNinja() && me.isStudent()) { return '/students' }
if (me.isCodeNinja() && me.isAPIClient()) { return '/api-dashboard' }
if (me.isCodeNinja() && me.isTeacher()) { return '/teachers/classes' }
if (me.isTarena()) { return 'http://kidtts.tmooc.cn/ttsPage/login.html' }
if (this.hideNav) { return '#' }
Expand Down
31 changes: 29 additions & 2 deletions app/core/api/background-job.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const fetchJson = require('./fetch-json')
const { sleep } = require('lib/common-utils')

function create (jobType, input, other) {
let url = '/db/background-jobs'
Expand All @@ -20,7 +21,33 @@ function get (jobId, other, options = {}) {
return fetchJson(url, options)
}

async function pollTillResult (jobId, other, options = {}) {
let url = `/db/background-jobs/${jobId}`
if (other) {
url = '/' + other + url
}
let job = await fetchJson(url, options)
const MAX_ATTEMPTS = 30
const DELAY_MS = 5000
let attempts = 0
while (job.status !== 'completed' && job.status !== 'failed' && attempts < MAX_ATTEMPTS) {
await sleep(DELAY_MS)
job = await fetchJson(url, options)
attempts++
}
if (job.status === 'completed') {
return JSON.parse(job.output)
}
if (job.status === 'failed') {
throw new Error(job.message)
}
if (attempts === MAX_ATTEMPTS) {
throw new Error('Failed to load the results')
}
}

module.exports = {
create,
get
}
get,
pollTillResult,
}
26 changes: 13 additions & 13 deletions app/core/store/modules/apiClient.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import apiclientsApi from 'core/api/api-clients'
import { create as createJob, pollTillResult } from 'core/api/background-job'

export default {
namespaced: true,
Expand Down Expand Up @@ -54,21 +55,20 @@ export default {
commit('setClientId', { id: clientId })
return clientId
},
fetchLicenseStats: ({ commit }, { clientId, startDate, endDate }) => {
fetchLicenseStats: async ({ commit }, { clientId, startDate, endDate }) => {
commit('toggleLoading', 'byLicense')
return apiclientsApi
.getLicenseStats(clientId, { startDate, endDate })
.then(res => {
if (res) {
commit('addLicenseStats', {
stats: res
})
} else {
throw new Error('Unexpected response from license stats by owner API.')
}
try {
const job = await createJob('api-client-stats', { clientId, startDate, endDate })
const res = await pollTillResult(job?.job)
commit('addLicenseStats', {
stats: res,
})
.catch((e) => noty({ text: 'Fetch license stats failure: ' + e, type: 'error' }))
.finally(() => commit('toggleLoading', 'byLicense'))
} catch (err) {
const message = err?.message || 'Failed to load the results'
noty({ text: 'Fetch license stats failure: ' + message, type: 'error' })
} finally {
commit('toggleLoading', 'byLicense')
}
},
fetchPlayTimeStats: ({ commit }) => {
commit('toggleLoading', 'byPlayTime')
Expand Down
7 changes: 6 additions & 1 deletion app/lib/common-utils.js
EDBE
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ function validateEmail(email) {
return re.test(String(email).toLowerCase());
}

function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}

module.exports = {
validateEmail
validateEmail,
sleep,
}
15 changes: 15 additions & 0 deletions app/models/Classroom.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,21 @@ module.exports = (Classroom = (function () {
return notification.$buttons.addClass('style-flat')
})
}

static codeNinjaClassroomTypes () {
return [
{ id: 'club-esports', name: 'Club Esports' },
{ id: 'club-hackstack', name: 'Club Hackstack' },
{ id: 'club-roblox', name: 'Club Roblox' },
{ id: 'club-ozaria', name: 'Club Ozaria' },
{ id: 'camp-esports', name: 'Camp Esports' },
{ id: 'camp-junior', name: 'Camp Junior' },
]
}

isCodeNinjaClubCamp () {
return Classroom.codeNinjaClassroomTypes().map(type => type.id).includes(this.get('type'))
}
}
Classroom.initClass()
return Classroom
Expand Down
5 changes: 5 additions & 0 deletions app/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,11 @@ module.exports = (User = (function () {
return this.isMtoStem() || this.isMtoNeo()
}

isGeccClient () {
const GECC_ID = '61e7e20658f1020024bd8cf7'
return this.get('_id') === GECC_ID
}
1E0A
Comment on lines +1479 to +1482
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve the GECC client identification implementation.

The current implementation has several issues that should be addressed:

+    // Constants
+    static GECC_CLIENT_ID = '61e7e20658f1020024bd8cf7'
+
+    /**
+     * Checks if the current user is a GECC client
+     * @returns {boolean} True if user is GECC client, false otherwise
+     */
     isGeccClient () {
-      const GECC_ID = '61e7e20658f1020024bd8cf7'
-      return this.get('_id') === GECC_ID
+      const userId = this.get('_id')
+      if (!userId) return false
+      return userId === User.GECC_CLIENT_ID
     }

The refactoring:

  1. Moves the client ID to a static class constant
  2. Adds JSDoc documentation
  3. Adds null check for user ID
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isGeccClient () {
const GECC_ID = '61e7e20658f1020024bd8cf7'
return this.get('_id') === GECC_ID
}
// Constants
static GECC_CLIENT_ID = '61e7e20658f1020024bd8cf7'
/**
* Checks if the current user is a GECC client
* @returns {boolean} True if user is GECC client, false otherwise
*/
isGeccClient () {
const userId = this.get('_id')
if (!userId) return false
return userId === User.GECC_CLIENT_ID
}


isManualClassroomJoinAllowed () {
if (this.isMto()) {
return false
Expand Down
3 changes: 0 additions & 3 deletions app/templates/courses/classroom-settings-modal.pug
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,6 @@ block modal-body-content
i.spl.text-muted(data-i18n="signup.optional")
select.form-control(name="type", value=view.classroom.get('type'))
option(value="" data-i18n="courses.avg_student_exp_select")
if me.isCodeNinja()
option(value="camp-esports" data-i18n="courses.class_type_camp_esports")
option(value="camp-junior" data-i18n="courses.class_type_camp_junior")
if !me.isCodeNinja()
option(value="in-school" data-i18n="courses.class_type_in_school")
option(value="after-school" data-i18n="courses.class_type_after_school")
Expand Down
61 changes: 33 additions & 28 deletions app/views/api/components/ApiData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
h2(v-if="loading") Loading...
.tab-select
.tab(@click="tab = 'byMonth'" :class="{active: tab === 'byMonth'}") By Time
.tab(@click="tab = 'byStudent'" :class="{active: tab === 'byStudent'}") By Student
.tab(v-if="!hideByStudentTab()" @click="tab = 'byStudent'" :class="{active: tab === 'byStudent'}") By Student
template(v-if="tab === 'byMonth'")
h2(v-if="licenseDaysByMonth && viewport === 'full'") License Days by Month
p(class="link-info") Access the new dashboard with graphs
a(href="/partner-dashboard?fromOld=1" target="_blank") {{ $t('general.here') }}
table.table.table-condensed(v-if="!licenseStatsLoading && viewport === 'full'")
tr(class="odd")
th.month.border {{ $t('library.month') }}
Expand Down Expand Up @@ -46,7 +48,7 @@
.age-stats(v-if="ageStats.length > 0")
d3-bar-chart(:datum="ageStats", :config="this.ageChartConfig()", title="Users Age Split", source="Age ranges")

template(v-else)
template(v-else-if="tab === 'byStudent'")
license-data-per-user(:loading="loading" :prepaids="prepaids" :teacherMap="teacherMap")

</template>
Expand All @@ -55,16 +57,18 @@
import { mapActions, mapState, mapGetters } from 'vuex'
import LicenseDataPerUser from 'app/components/license/LicenseDataPerUser'
import { D3BarChart } from 'vue-d3-charts'

module.exports = Vue.extend({
components: {
LicenseDataPerUser,
D3BarChart
D3BarChart,
},
props: ['viewport'],
data () {
return {
tab: 'byMonth',
spiedUser: window.serverSession.amActually
spiedUser: window.serverSession.amActually,
myId: me.get('_id'),
}
},
computed: {
Expand Down Expand Up @@ -179,6 +183,25 @@ module.exports = Vue.extend({
return data
}
},
watch: {
clientId: function (id) {
if (id !== '') {
this.fetchTeachers(id)
this.fetchPrepaids({ teacherId: this.myId, clientId: id })
this.fetchLicenseStats({ clientId: id })
}
},
},
created () {
if (me.isGeccClient()) {
this.tab = 'byStudent'
}

this.fetchClientId()
// current play time for apiclient is the total time of all students so i think
// we doesn't need it now
/* this.fetchPlayTimeStats() */
},
methods: {
...mapActions({
fetchLicenseStats: 'apiClient/fetchLicenseStats',
Expand All @@ -197,32 +220,14 @@ module.exports = Vue.extend({
yTitle: 'Percentage of users',
xTitle: 'Age Ranges',
xFormat: '.0f',
xTicks: 0
}
xTicks: 0,
},
}
}
},
watch: {
clientId: function (id) {
if (id !== '') {
this.fetchTeachers(id)
this.fetchPrepaids({ teacherId: this.myId, clientId: id })
this.fetchLicenseStats({ clientId: id })
}
}
},
hideByStudentTab () {
return !me.isGeccClient()
},
},
created () {
this.myId = me.get('_id')
const geccId = '61e7e20658f1020024bd8cf7'
if (this.myId.toString() === geccId) {
this.tab = 'byStudent'
}

this.fetchClientId()
// current play time for apiclient is the total time of all students so i think
// we doesn't need it now
/* this.fetchPlayTimeStats() */
}
})
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import BannerHoC from 'app/views/courses/BannerHoC'
import ButtonsSchoolAdmin from './ButtonsSchoolAdmin'
import PodcastItemContainer from 'app/views/courses/PodcastItemContainer'
import sortClassroomMixin from '../mixins/sortClassroomMixin.js'
import clubCampMixin from '../mixins/clubCampMixin'

export default {
name: COMPONENT_NAMES.MY_CLASSES_ALL,
Expand All @@ -23,7 +24,8 @@ export default {
},

mixins: [
sortClassroomMixin
sortClassroomMixin,
clubCampMixin,
],

props: {
Expand Down Expand Up @@ -128,8 +130,8 @@ export default {
this.showShareClassWithTeacherModal = true
this.editClassroomObject = classroom
},
showCreateStudents (classroom) {
return me.isCodeNinja() && (classroom.type?.includes('club') || classroom.type === 'camp-esports')
showCreateStudents (_classroom) {
return false
},
},
}
Expand Down Expand Up @@ -243,6 +245,7 @@ export default {
<modal-edit-class
v-if="showEditClassModal"
:classroom="editClassroomObject"
:as-club="isCodeNinjaClubCamp(editClassroomObject)"
@close="showEditClassModal = false"
/>
<modal-add-students
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { FIRST_CLASS_STEPS, CREATE_CLASS_STEPS } from './teacherDashboardTours'
import ModalTeacherDetails from '../modals/ModalTeacherDetails'
import { hasSeenTeacherDetailModalRecently, markTeacherDetailsModalAsSeen } from '../../../common/utils'
import TryOzariaModal from 'app/components/teacher/TryOzariaModal.vue'
import clubCampMixin from '../mixins/clubCampMixin'

const VueShepherd = require('vue-shepherd')

Expand All @@ -42,8 +43,9 @@ export default {
LoadingBar,
ModalTeacherDetails,
TryOzariaModal,
TopBanner
TopBanner,
},
mixins: [clubCampMixin],

data () {
// TODO: move the logic to open/close modals to teacherDashboard store instead of driving by events,
Expand Down Expand Up @@ -375,8 +377,8 @@ export default {
storage.save(TRY_OZ_MODAL_VIEWED_KEY, true, oneMonth)
this.showTryOzariaModal = false
},
shouldShowCreateStudents (classroom) {
return me.isCodeNinja() && (classroom.type?.includes('club') || classroom.type === 'camp-esports')
shouldShowCreateStudents (_classroom) {
return false
},
},
}
Expand Down Expand Up @@ -458,6 +460,7 @@ export default {
<modal-edit-class
v-if="showNewClassModal && editCurrent"
:classroom="editClassroomObject"
:as-club="isClub(editClassroomObject)"
@close="closeShowNewModal"
/>
<modal-assign-content
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Classroom from 'models/Classroom'

export default {
methods: {
isCodeNinjaClubCamp (classroom) {
return Classroom.codeNinjaClassroomTypes().map(type => type.id).includes(classroom?.type)
},
},
}
Loading
Loading
0